49

LifecycleOwner is currently needed in order for me to create an observer.

I have code which creates an Observer in the ViewModel so I attach the LifecycleOwner when retrieving the ViewModel in my Fragment.

According to Google's documentation.

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

Did I break that warning and If I did, what way do you recommend me to move my creation of an observer for data return?

I only made an observer so I'm wondering if it's still valid. Since also in Google's documentation it also said.

ViewModel objects can contain LifecycleObservers, such as LiveData objects.

MainFragment

private lateinit var model: MainViewModel

/**
 * Observer for our ViewModel IpAddress LiveData value.
 * @see Observer.onChanged
 * */
private val ipObserver = Observer<String> {
    textIp.text = it
    hideProgressBar()
}

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    model = ViewModelProviders.of(this).get(MainViewModel::class.java)
    model.attach(this)
}

override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? =
        inflater?.inflate(R.layout.fragment_main, container, false)

override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    buttonRetrieveIp.setOnClickListener {
        showProgressBar()
        model.fetchMyIp().observe(this, ipObserver) //Here we attach our ipObserver
    }
}

override fun showProgressBar() {

    textIp.visibility = View.GONE
    progressBar.visibility = View.VISIBLE
}

override fun hideProgressBar() {

    progressBar.visibility = View.GONE
    textIp.visibility = View.VISIBLE
}

MainViewModel

private var ipAddress = MutableLiveData<String>()
private lateinit var owner: LifecycleOwner

fun attach(fragment: MainFragment) {
    owner = fragment
}

/**
 * For more information regarding Fuel Request using Fuel Routing and Live Data Response.
 * @see <a href="https://github.com/kittinunf/Fuel#routing-support">Fuel Routing Support</a>
 * @see <a href="https://github.com/kittinunf/Fuel#livedata-support">Fuel LiveData Support</a>
 * */
fun fetchMyIp(): LiveData<String> {

    Fuel.request(IpAddressApi.MyIp())
            .liveDataResponse()
            .observe(owner, Observer {

                if (it?.first?.statusCode == 200) {//If you want you can add a status code checker here.

                    it.second.success {

                        ipAddress.value = Ip.toIp(String(it))?.ip
                    }
                }
            })
    return ipAddress
}

Update 1: Improved ViewModel thanks to @pskink suggestion for using Transformations.

private lateinit var ipAddress:LiveData<String>

/**
 * Improved ViewModel since January 23, 2018 credits to <a href="https://stackoverflow.com/users/2252830/pskink">pskink</a> <a href="
 *
 * For more information regarding Fuel Request using Fuel Routing and Live Data Response.
 * @see <a href="https://github.com/kittinunf/Fuel#routing-support">Fuel Routing Support</a>
 * @see <a href="https://github.com/kittinunf/Fuel#livedata-support">Fuel LiveData Support</a>
 * */
fun fetchMyIp(): LiveData<String> {

    ipAddress = Transformations.map(Fuel.request(IpAddressApi.MyIp()).liveDataResponse(), {

        var ip:String? = ""

            it.second.success {

                ip = Ip.toIp(String(it))?.ip
            }
        ip
    })

    return ipAddress
}
Hiroga Katageri
  • 1,345
  • 3
  • 21
  • 40
  • 1
    tried `MediatorLiveData` or `Transformations#map` / `Transformations#switchMap`? – pskink Jan 23 '18 at 07:34
  • I haven't yet actually, so instead of returning a LiveData String, I'll return a MediatorLiveData and add both sources and add a single observer in my fragment right? – Hiroga Katageri Jan 23 '18 at 07:48
  • 1
    first try `Transformations` class - it seems that it is the easiest way - if not, when it comes to `MediatorLiveData` what you mean by "both sources"? there is one source that changes, isnt it? – pskink Jan 23 '18 at 07:49
  • My bad, I only get the source from my GET method. Hmm... I tried wrapping the liveDataResponse() in the Transformations.map but it doesn't proceed to call my GET method. http://prntscr.com/i4bnhe I'm definitely doing something wrong here. – Hiroga Katageri Jan 23 '18 at 09:26
  • 1
    see [Transform LiveData](https://developer.android.com/topic/libraries/architecture/livedata.html#transform_livedata) – pskink Jan 23 '18 at 09:30
  • 1
    I got it now, I was able to make it work using Transformations. – Hiroga Katageri Jan 23 '18 at 09:40

4 Answers4

75

No. If you wish to observe changes of some LiveData inside your ViewModel you can use observeForever() which doesn't require LifecycleOwner.

Remember to remove this observer on ViewModel's onCleared() event:

val observer = new Observer() {
  override public void onChanged(Integer integer) {
    //Do something with "integer"
  }
}

...

liveData.observeForever(observer);

...

override fun onCleared() {
    liveData.removeObserver(observer) 
    super.onCleared()
}

Very good reference with examples of observe LiveData.

codingjeremy
  • 5,215
  • 1
  • 36
  • 39
Vitaliy A
  • 3,651
  • 1
  • 32
  • 34
2

Assumptions:

  1. Fuel refers to your ViewModel
  2. Fuel.request(IpAddressApi.MyIp()) is a method in your ViewModel
  3. IpAddressApi.MyIp() does not have a reference to your LifecycleOwner,

If all are true,then you are not violating it. So long as you are not passing a LifecycleOwner reference to the ViewModel you are safe!

LifecycleOwner - relates to an Activity or Fragment as it owns the various Android Lifecycles e.g onCreate, onPause, onDestroy etc

martinomburajr
  • 1,235
  • 14
  • 29
  • I've updated my question with additional information. Fuel is my HttpClient, Fuel.request is a method in Fuel that converts a FuelRouting Interface to a request, which is where I create an observer for the return from my GET method. – Hiroga Katageri Jan 23 '18 at 07:40
1

in Kotlin this can be something like:

val mObserver = Observer<List<QueueTabData>> { myString->
// do something with myString
}
Choletski
  • 7,074
  • 6
  • 43
  • 64
1

Should I include LifecycleOwner in ViewModel?
Ans: No

The purpose of viewmodel is to hold UI data, so that it survives across configuration changes.
And the reason for the following

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

Is because the viewmodel survives configuration changes whereas activities don't. They are destroyed and re-created on configuration change. So, if you have any activity context references in viewmodel they would refer to previous activity that got destroyed.

So this leads to memory leak. And hence it is not recommended.

Furthermore,

If you have repositories that act as your data source we should avoid using LiveData for such purposes as mentioned here in the paragraph just above the code block.
This is because LiveData are handled on MainThread that may lead to UI freeze.

We should use kotlin flows for such purposes.

Mr. Techie
  • 622
  • 7
  • 17