original source : https://medium.com/keepsafe-engineering/an-in-depth-look-at-kotlins-initializers-a0420fcbf546

image

Constructors vs Initializers

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?

Here’s the output:

Child secondary constructor default argument
Child primary constructor default argument
Parent primary constructor default argument
Parent.a
Parent.init
Parent.b
Parent primary constructor
Child.a
Child.init 1
Child.b
Child.init 2
Child primary constructor
Child secondary constructor

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.

original source : https://youtu.be/M15PEeHXM64 

33분량

select list형태의 경우 아래와 같이 array를 만들어 사용한다.

image

.

.

preference activity 나 preference fragment에 사용할 화면 구성하기 

image
image
image

.

.

preference activity 나 preference fragment의 형태 이경우에는 preference activity 안에 preference fragment를 포함 하는 형태로 만들었는데 반드시 포함해야 하는 것은 아니다.

image

.

.

저장된 Preferences를 통해 저장된 SharedPreferences값을 불러오는 방법과 일반 SharedPreferences사용방법

image

.

.

SharedPreferences와 Preferences 차이

https://stackoverflow.com/a/23223316/3151712

.

.

.

.

https://youtu.be/M15PEeHXM64

30분 분량

.

.

.

.

https://youtu.be/SfeRakSWWbk

16분분량

.

.

.

.

SharedPreferences Preferences setOnPreferenceChangeListener onPreferenceChange alert alertdialog

https://stackoverflow.com/a/21857176/3151712

https://stackoverflow.com/a/11531922/3151712

dialogpreference

https://developer.android.com/reference/android/preference/DialogPreference?hl=en

extend 

https://stackoverflow.com/a/40058062/3151712

.

.

.

.

Preference EditTextPreference 속성 attributes 바꾸기

https://stackoverflow.com/a/55461028/3151712

wasent개발하면서 알게 된점

  • 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)

ref)kotlin https://stackoverflow.com/a/52684990/3151712

ref)official docs  https://firebase.google.com/docs/reference/android/com/google/firebase/database/GenericTypeIndicator

.

.

.

.

.

google firebase realtime database official docs
https://firebase.google.com/docs/database/android/start

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의 데이터를 가져온다는 이야기는 하부의 모든 데이터도 같이 가져온다는 이야기가 되기 때문에 염두에 둔다.

https://firebase.google.com/docs/database/android/read-and-write

각 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 스스로 삭제된다)

image

위 그림에서 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도 가능하다.)

image

Simultaneous updates made this way are atomic: either all updates succeed or all updates fail.

setValue() and updateChildren() 작업에는 아래와 같이 listener를 달아서 바로 결과를 확인할수 있다. (setValue()로 create, delete,update작업을 할수 있으므로 모든 작업에 listener를 아래와 같이 달아서 확인 가능) 

image

특정 node에 덧붙여진 listener를 removeEventListener() 를 통해 제거해야 하며 덧붙여진 갯수 만큼 제거 해야 하고 . child node에 있는 것이 자동으로 제거되는 건 아니므로 child node에 따로 접근 제거해야 한다.

https://firebase.google.com/docs/database/android/lists-of-data

collection 형태의 자료를 가지고 있는 node의 경우 node에 ChildEventListener를 붙이면 onChildAdded, onChildChanged, onChildRemoved 을 통해 돌아 오는 snapshot이 각각 하나의 element가 된다. ValueEventListener를 사용하는 경우는 snapshot이 collection전체를 가지고 있게 된다. 이때    for (postSnapshot in dataSnapshot.children) {}    이런 형태로 접근하게 된다.

image

데이터 정렬방법은 위와 같으며 단 한개만 설정할수 있다.

image

위그림은 먼저 정렬된 상태에서 ChildEventListener를 붙이는 모습을 보여준다.

image

위그림은 nested child를 경로 지정으로 정렬기준을 조정하는 방법

image

위 query method는 여러개를 사용할수 있다.

query 성능 개선을 위해서는 index를 이용할수 있다. ( https://firebase.google.com/docs/database/security/indexing-data )

.

.

.

.

original source : https://www.youtube.com/playlist?list=PLk7v1Z2rk4hg3cbALQuTQOTgpZ9e9T8bi

image

.

.

image

.

.

image

addListenerForSingleValueEvent 현재 상태에서 snapshot가져오는 함수

addValueEventListener 하나의 노드 실시간 감시 

addCildEventListener 자식노드까지 실시간 감시

.

.

image

.

.

image
image

.

.

image
image

.

.

image
image

.

.

image
image
image

.

.

firebase realtime database

에는 두가지조건을 동시에 검색하는것이 없다.

image

이런것을 불가능하고

또한 두테이블의 relation 도 만들수 없다.

original source : https://stackoverflow.com/a/58663143/3151712

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:

  1. Putting the Fragment on the back stack (i.e., when you navigate() to another Fragment)
  2. Calling detach() on a Fragment
  3. 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.

https://stackoverflow.com/a/57630353/3151712

질문) ViewModelProviders is deprecated So what’s the alternative to create the ViewModel’s object ?

답) 

boardViewModel = ViewModelProvider(this).get(BoardViewModel::class.java)

답)

private val viewModel: EntityGridViewModel by viewModels()

위의 방법으로 사용했지만 constructor가 있는 ViewModel의 경우 위의 구문실행시기가 이른 시기에 해당해서 constructor에 전달될 parameter를 준비하는데 시간이 부족한 경우가 많다. 그래서 다른 방법을 사용하게 되었다. 

https://www.albertgao.xyz/2018/04/13/how-to-add-additional-parameters-to-viewmodel-via-kotlin/

binding.authViewModel = ViewModelProviders.of(
 this,
 viewModelFactory { MyViewModel("albert") }
).get(AuthViewModel::class.java)

참고자료) passing parameters viewmodel

https://stackoverflow.com/a/60130631/3151712

https://www.albertgao.xyz/2018/04/13/how-to-add-additional-parameters-to-viewmodel-via-kotlin/

.

.

.

https://developer.android.com/topic/libraries/architecture/viewmodel

ViewModel 전반적인 사용 방법

.

.

위와 같이 사용하려고 하였으나 자동완성이 되지 않았다. dependency 설정이 잘못되어있었기 때문이다. 

    // ViewModel
//    android docs ref) https://developer.android.com/jetpack/androidx/releases/lifecycle
   def lifecycle_version = "2.2.0"
   def arch_version = "2.1.0"
   implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
   // LiveData
   implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"

//    ref) https://developer.android.com/kotlin/ktx#fragment
   implementation "androidx.fragment:fragment-ktx:1.2.5"

   //ViewModel
//    from codelab ref) https://codelabs.developers.google.com/codelabs/kotlin-android-training-view-model/index.html?index=..%2F..android-kotlin-fundamentals#4
   implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'

가 필요했다. 

https://developer.android.com/topic/libraries/architecture/viewmodel#sharing

https://developer.android.com/kotlin/ktx#fragment

implementation "androidx.fragment:fragment-ktx:1.2.5"

.

.

.

.

주의사항)

https://developer.android.com/topic/libraries/architecture/viewmodel

Caution: A ViewModel must never reference a view, Lifecycle, or any class that may hold a reference to the activity context.

image

.

.

.

.

하나의 activity 아래에 여러개의 fragment가 있고 activity lifecycle에 맞춰서 share view model을 사용하는 방법 

기본적으로 아래 내용은 그대로 이나 약간 문법이 바뀌었다. 

변화된 내용은 https://developer.android.com/topic/libraries/architecture/viewmodel#sharing 에서 확인할수 있다. 변화된 코드는 아래와 같다. 먼저 바로 아래 것을 보고 밑에것을 보면 된다. 

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()

   override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
       super.onViewCreated(view, savedInstanceState)
       model.selected.observe(viewLifecycleOwner, Observer<Item> { item ->
           // Update the UI
       })
   }
}

.

https://medium.com/mindorks/how-to-communicate-between-fragments-and-activity-using-viewmodel-ca733233a51c

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

activity?.let {
   sharedViewModel = ViewModelProviders.of(it).get(SharedViewModel::class.java)
}

et_input.addTextChangedListener(object : TextWatcher {
   override fun afterTextChanged(p0: Editable?) {}

   override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}

   override fun onTextChanged(txt: CharSequence?, p1: Int, p2: Int, p3: Int) {
       txt?.let {
           var input = 0
           if (txt.toString().isNotEmpty()) {
               input = txt.toString().toInt()
           }

           sharedViewModel?.inputNumber?.postValue(input)
       }
   }

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
       }
   })
}

That’s it. Source code can be found here.

.

source code

class InputFragment : Fragment() {

    private var sharedViewModel: SharedViewModel? = null

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_input, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        activity?.let {
            /**
             *  create view model in activity scope
             */
            sharedViewModel = ViewModelProviders.of(it).get(SharedViewModel::class.java)
        }

        et_input.addTextChangedListener(object : TextWatcher {
            override fun afterTextChanged(p0: Editable?) {}

            override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}

            override fun onTextChanged(txt: CharSequence?, p1: Int, p2: Int, p3: Int) {
                txt?.let {
                    var input = 0
                    if (txt.toString().isNotEmpty()) {
                        input = txt.toString().toInt()
                    }

                    sharedViewModel?.inputNumber?.postValue(input)
                }
            }
        })
    }

}
package ninja.shuza.sharedviewmodel

import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModelProviders
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
import kotlin.properties.Delegates

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        supportFragmentManager.beginTransaction().add(R.id.layout_top, InputFragment()).commit()
        supportFragmentManager.beginTransaction().add(R.id.layout_bottom, OutputFragment()).commit()

        val message = resources.getString(R.string.show_input)

        val sharedViewModel = ViewModelProviders.of(this).get(SharedViewModel::class.java)

        sharedViewModel.inputNumber.observe(this, Observer {
            it?.let {
                tv_show_input.text = "$message  $it"
            }
        })
    }
}
package ninja.shuza.sharedviewmodel

import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModelProviders
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import kotlinx.android.synthetic.main.fragment_output.*

/**
 *
 * :=  created by:  Shuza
 * :=  create date:  09-Aug-2018
 * :=  (C) CopyRight Shuza
 * :=  www.shuza.ninja
 * :=  shuza.sa@gmail.com
 * :=  Fun  :  Coffee  :  Code
 *
 **/
class OutputFragment : Fragment() {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_output, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        /**
         *  create view model in activity scope
         */
        activity?.let {
            val sharedViewModel = ViewModelProviders.of(it).get(SharedViewModel::class.java)

            observeInput(sharedViewModel)
        }
    }

    private fun observeInput(sharedViewModel: SharedViewModel) {
        sharedViewModel.inputNumber.observe(this, Observer {
            it?.let {
                tv_output.text = "2 x $it  =  ${2 * it}"
            }
        })
    }

}

.

.

.

.

share viewmodel with several fragment

original source : https://medium.com/mindorks/how-to-communicate-between-fragments-and-activity-using-viewmodel-ca733233a51c

image

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

activity?.let {
   sharedViewModel = ViewModelProviders.of(it).get(SharedViewModel::class.java)
}

et_input.addTextChangedListener(object : TextWatcher {
   override fun afterTextChanged(p0: Editable?) {}

   override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}

   override fun onTextChanged(txt: CharSequence?, p1: Int, p2: Int, p3: Int) {
       txt?.let {
           var input = 0
           if (txt.toString().isNotEmpty()) {
               input = txt.toString().toInt()
           }

           sharedViewModel?.inputNumber?.postValue(input)
       }
   }

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
       }
   })
}

That’s it. Source code can be found here.