One aspect of class creation in Kotlin that might surprise people coming from other languages is that, when writing a class, there a few places that you can define code that will be executed when you create an instance of the class:
Property initializers
When you declare a property, you can immediately assign it a value like this:
val count: Int = 0
You’ll frequently use constant values as initializers, but you can also call a function or use a property delegate in order to run arbitrary code.
Initializer blocks
If you want more complex code, you can also use an initializer block, defined with the init keyword:
init { /* do some setup here */ }
You can define those blocks anywhere at the top level of a class declaration, and they will be executed as part of class construction. You can even define more than one initializer block if you want, and they will all be executed.
Constructors
Kotlin also has constructors, which can be defined in the class header or in the body of the class definition. You can define multiple secondary constructors, but only one will be called when you create a class instance unless the constructor explicitly calls another one. Constructors can also have default argument values which are evaluated each time the constructor is called. Like property initializers, these can be function calls or other expressions that will run arbitrary code.
Execution order
All of those features are great, but it can be easy to overlook how they all interact, especially when inheritance is involved. So what order will code run if you define a class with all of them?
First, default constructor arguments are evaluated, starting with argument to the constructor you call directly, followed by arguments to any delegated constructors. Next, initializers (property initializers and init blocks) are executed in the order that they are defined in the class, top-to-bottom. Finally, constructors are executed, starting with the primary constructor and moving outward through delegated constructors until the constructor that you called is executed. The constructor order is probably the most surprising, since no matter where in the class the constructor is defined, it is always executed after all initializers have run.
That’s a lot of rules, so let’s create an small program to help us visualize all of this:
open class Parent {
private val a = println("Parent.a")
constructor(arg: Unit=println("Parent primary constructor default argument")) {
println("Parent primary constructor")
}
init {
println("Parent.init")
}
private val b = println("Parent.b")
}
class Child : Parent {
val a = println("Child.a")
init {
println("Child.init 1")
}
constructor(arg: Unit=println("Child primary constructor default argument")) : super() {
println("Child primary constructor")
}
val b = println("Child.b")
constructor(arg: Int, arg2:Unit= println("Child secondary constructor default argument")): this() {
println("Child secondary constructor")
}
init {
println("Child.init 2")
}
}
If we construct an instance of Child by calling its secondary constructor with Child(1), what will be printed to the console?
As you can see, initializers are run top to bottom at the beginning of a class’ primary constructor. If you call a secondary constructor, the constructor that gets delegated to will be run before the secondary constructor. And most importantly, superclasses will be fully constructed before any subclass constructors will be run.
addListenerForSingleValueEvent 를 통해서 snapshot을 얻어올수도 있지만 최초로 addCildEventListener를 node에 연결할때 되돌아 오는 snapshot을 이용할수 있다. 단 addCildEventListener 의 경우 listener가 연결된 지점이하 child 들의 snapshot이 한번에 오는 것이 아니고 순차적으로 하나씩오게 된다.
node의 key, value개념과 child개념을 혼동하면 안된다.
children을 통해 child들을 통으로 가져오려는 경우 children의 parent node에 listener를 연결한다. 때때로 wrapper parent를 만들어야 하는경우도 있다.
getValue()사용시 collection형태로 data를 가져오려는 경우 GenericTypeIndicator를 이용한다.
val type = object : GenericTypeIndicator<HashMap<String,String>>() {}
val precios : HashMap<String,String> = dataSnapshot.getValue(type!!)
liveData.postValue(precios)
To make your app data update in realtime, you should add a ValueEventListener to the reference you just created. (ChildEventListerner는 현 node아래 child를 관찰하는 것이고 ValueEventListener는 현재node 및 child를 관찰하는 것이다)
The onDataChange() method in this class is triggered once when the listener is attached and again every time the data changes, including the children.
All Firebase Realtime Database data is stored as JSON objects. 그러므로 json으로 표현되기 쉬운 형태로 database구성을 생각한다.
jacob.body.head.eyes.pupil 이런식으로 저장된다고 생각하고 jacob body head eyes pupil은 key이름이며 이안에 다양한 타입의 data가 들어있다. 또 jacob boddy head eyes pupil 은 각각 node이다.
t’s best to keep your data structure as flat as possible. 너무 많이 nested되지 않게 하며 각 node의 데이터를 가져온다는 이야기는 하부의 모든 데이터도 같이 가져온다는 이야기가 되기 때문에 염두에 둔다.
각 node의 reference에서 setValue()를 통해 값을 쓰고 교환하고 지우기(null을 설정)를 할수 있다.
저장가능한 데이터 형태는 아래와 같다.
Pass types that correspond to the available JSON types as follows:
String
Long
Double
Boolean
Map<String, Object>
List<Object>
Pass a custom Java object,
if the class that defines it has a default constructor that takes no arguments and has public getters for the properties to be assigned.
To read data at a path and listen for changes, use the addValueEventListener() or addListenerForSingleValueEvent() method to add a ValueEventListener to a DatabaseReference. (addValueEventListener 나 addListenerForSingleValueEvent 둘다 ValueEventListener를 사용하는 것은 같다. 다만addListenerForSingleValueEvent는 한번 데이터 snapshot을 가져오고 listener 스스로 삭제된다)
위 그림에서 ValueEventListener also defines the onCancelled() method that is called if the read is canceled. For example, a read can be canceled if the client doesn’t have permission to read from a Firebase database location.
updateChildren()을 이용 해당 node의 여러 child node의 데이터를 하나의 작업으로 수정가능하다. (update만 가능한게 아니라 create도 가능하다.)
Simultaneous updates made this way are atomic: either all updates succeed or all updates fail.
setValue() and updateChildren() 작업에는 아래와 같이 listener를 달아서 바로 결과를 확인할수 있다. (setValue()로 create, delete,update작업을 할수 있으므로 모든 작업에 listener를 아래와 같이 달아서 확인 가능)
특정 node에 덧붙여진 listener를 removeEventListener() 를 통해 제거해야 하며 덧붙여진 갯수 만큼 제거 해야 하고 . child node에 있는 것이 자동으로 제거되는 건 아니므로 child node에 따로 접근 제거해야 한다.
collection 형태의 자료를 가지고 있는 node의 경우 node에 ChildEventListener를 붙이면 onChildAdded, onChildChanged, onChildRemoved 을 통해 돌아 오는 snapshot이 각각 하나의 element가 된다. ValueEventListener를 사용하는 경우는 snapshot이 collection전체를 가지고 있게 된다. 이때 for (postSnapshot in dataSnapshot.children) {} 이런 형태로 접근하게 된다.
데이터 정렬방법은 위와 같으며 단 한개만 설정할수 있다.
위그림은 먼저 정렬된 상태에서 ChildEventListener를 붙이는 모습을 보여준다.
There are two different lifecycles because the Fragment itself lives longer than the Fragment’s view.
There are a number of events that can cause the Fragment’s view to be destroyed, but currently keep the Fragment itself alive:
Putting the Fragment on the back stack (i.e., when you navigate() to another Fragment)
Calling detach() on a Fragment
Calling setMaxLifecycle(Lifecycle.State.CREATED) on a Fragment
In these cases, the Fragment’s view is destroyed, moving the Fragment view lifecycle to DESTROYED. However, the lifecycle of Fragment itself is not destroyed – it generally stays as CREATED. This means that the Fragment can go through multiple cycles of onCreateView() -> onViewCreated() -> onDestroyView() while only going through onCreate() once.
Where this becomes a problem is when it comes to how LiveData works. When you observe a LiveData, LiveData automatically unregisters the observer when the lifecycle reaches DESTROYED. But if you use the Fragment’s lifecycle to observe in onCreateView(), etc., that registered observer will still exist after onDestroyView(), despite the view being destroyed. This means that the second time your Fragment goes through onCreateView(), you’ll actually create a second active Observer, with both running simultaneously. Then three Observers the next time and on and on.
By using the view LifecycleOwner in onCreateView()/onViewCreated(), you ensure that you’ll only have one active Observer running at a time and that Observers tied to previous view instances are correctly destroyed along with the view. Therefore, yes, you should always use getViewLifecycleOwner() as the LifecycleOwner when in onCreateView() or onViewCreated(), including when using Data Binding.
Of course, if you’re registering an Observer in onCreate(), then the view LifecycleOwner does not exist yet (it is created right before onCreateView()) and you don’t have the multiple registration problem, which is why the Lint check specifically does not apply to any registrations done at onCreate() time. In those cases, using the Fragment’s lifecycle itself is absolutely correct.
As per the Fragments: Past, Present, and Future talk, one future improvements for Fragments is going to be combining the two lifecycles together, always destroying the Fragment whenever the Fragment’s view is destroyed. This is not yet available in any shipped version of Fragments, alpha or otherwise.
class SharedViewModel : ViewModel() { val selected = MutableLiveData<Item>()
fun select(item: Item) { selected.value = item } }
class MasterFragment : Fragment() {
private lateinit var itemSelector: Selector
// Use the 'by activityViewModels()' Kotlin property delegate // from the fragment-ktx artifact private val model: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) itemSelector.setOnClickListener { item -> // Update the UI } } }
class DetailFragment : Fragment() {
// Use the 'by activityViewModels()' Kotlin property delegate // from the fragment-ktx artifact private val model: SharedViewModel by activityViewModels()
Here comes ViewModel to rescue us from handling a lot of scenario and implementing interface . We only need to create ViewModel class and create instance in fragment but using the activity scope so that it will be available for all the fragment of the activity including activity itself.
Create a ViewModel class
class SharedViewModel:ViewModel(){ val inputNumber = MutableLiveData<Int>() }
To emit or pass data from our input fragment create ViewModel in activity scope. To do this we have to pass the activity reference as argument of the ViewModelProvides.of() method. Noe just pass the data to ViewModel object like this
In the activity we just need to create instance of our ViewModel and observe the required data like this
val sharedViewModel = ViewModelProviders.of(this).get(SharedViewModel::class.java)
sharedViewModel.inputNumber.observe(this, Observer { it?.let { // do some thing with the number } })
Now what about output fragment? We can do same for the output fragment to observe the data. But keep in mind that we need create the ViewModel instance in activity scope, otherwise android will create a separate instance rather than sharing the same instance and we will not get the data.
For output fragment do it like this
activity?.let { val sharedViewModel = ViewModelProviders.of(it).get(SharedViewModel::class.java)
sharedViewModel.inputNumber.observe(this, Observer { it?.let { // do some thing with the number } }) }
Here comes ViewModel to rescue us from handling a lot of scenario and implementing interface . We only need to create ViewModel class and create instance in fragment but using the activity scope so that it will be available for all the fragment of the activity including activity itself.
Create a ViewModel class
class SharedViewModel:ViewModel(){ val inputNumber = MutableLiveData<Int>() }
To emit or pass data from our input fragment create ViewModel in activity scope. To do this we have to pass the activity reference as argument of the ViewModelProvides.of() method. Noe just pass the data to ViewModel object like this
In the activity we just need to create instance of our ViewModel and observe the required data like this
val sharedViewModel = ViewModelProviders.of(this).get(SharedViewModel::class.java)
sharedViewModel.inputNumber.observe(this, Observer { it?.let { // do some thing with the number } })
Now what about output fragment? We can do same for the output fragment to observe the data. But keep in mind that we need create the ViewModel instance in activity scope, otherwise android will create a separate instance rather than sharing the same instance and we will not get the data.
For output fragment do it like this
activity?.let { val sharedViewModel = ViewModelProviders.of(it).get(SharedViewModel::class.java)
sharedViewModel.inputNumber.observe(this, Observer { it?.let { // do some thing with the number } }) }