Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

java.lang.IllegalArgumentException: This source was already added with the different observer

Can anyone please explain this expression properly for me... It seems to be the problem I'm facing currently.

MediatorLiveData#addSource

Starts to listen the given source LiveData, onChanged observer will be called when source value was changed.

onChanged callback will be called only when this MediatorLiveData is active.

If the given LiveData is already added as a source but with a different Observer, IllegalArgumentException will be thrown.

I currently have the following as my ViewModel (Called SplashViewModel)

package com.testapp.testapp.ui.splash;

import com.testapp.testapp.repository.HealthTipRepository;

import javax.inject.Inject;

import androidx.lifecycle.LiveData;
import androidx.lifecycle.MediatorLiveData;
import androidx.lifecycle.ViewModel;


public class SplashViewModel extends ViewModel {

    private final HealthTipRepository healthTipRepository;

    // Load Status will be used to fill up the progress bar inside the Activity
    private final MediatorLiveData<Integer> loadStatus = new MediatorLiveData<>();
    
    @Inject
    SplashViewModel(HealthTipRepository healthTipRepository) {
        this.healthTipRepository = healthTipRepository;
    }

    LiveData<Integer> observeLoadStatus() {
        loadStatus.addSource(healthTipRepository.createHealthTips(), healthTips -> {
            int currentValue = loadStatus.getValue();
            int newValue = currentValue != null ? currentValue + 25 : 25;
            loadStatus.setValue(newValue);
        });
    }
}

In the activity, I have this:

package com.testapp.testapp.ui.splash;

import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.LocationManager;
import android.os.Bundle;
import android.provider.Settings;
import android.view.View;
import android.widget.ProgressBar;

import com.testapp.testapp.R;
import com.testapp.testapp.storage.PrefManager;
import com.testapp.testapp.ui.BaseActivity;
import com.testapp.testapp.ui.FactoryViewModel;

import javax.inject.Inject;

import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProviders;
import butterknife.BindView;

// Base Activity has already injected the Dagger component
public class SplashActivity extends BaseActivity {

    @BindView(R.id.splash_progress)
    ProgressBar progressBar;

    @Inject
    FactoryViewModel factoryViewModel;
    private SplashViewModel viewModel;

    // PrefManager is responsible for managing shared preferences. It exposes a .get Method
    @Inject
    PrefManager prefManager;

    @Override
    protected void onCreate(@Nullable Bundle savedInstance) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.splash_activity);

        ButterKnife.bind(this);

        viewModel = ViewModelProviders.of(this, factoryViewModel).get(SplashViewModel.class);
    }

    @Override
    protected void onResume() {
        super.onResume();

        // Performs checks to turn on location. The viewmodel is placed in the
        // onREsume to ensure that even when we leave the activity to turn on the
        // location and return, we can always start the viewmodel
        boolean turnedOnLocation = false;
        if (!turnedOnLocation) {
            startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
        }

        boolean appSetup = prefManager.get("app_setup", false);
        if (!appSetup) {
            viewModel.observeLoadStatus().observe(this, status -> {
                progressBar.setProgress(status + "");
            });
        }
    }
}

Everything runs as smoothly, however, when I leave this activity and return, the application crashes with the error:

Process: com.testapp.testapp, PID: 29865
java.lang.RuntimeException: Unable to resume activity {com.testapp.testapp/com.testapp.testapp.ui.splash.SplashActivity}: java.lang.IllegalArgumentException: This source was already added with the different observer
    at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3609)
    at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3649)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1663)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at android.os.Looper.loop(Looper.java:164)
    at android.app.ActivityThread.main(ActivityThread.java:6524)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:888)
 Caused by: java.lang.IllegalArgumentException: This source was already added with the different observer
    at androidx.lifecycle.MediatorLiveData.addSource(MediatorLiveData.java:89)
    at com.testapp.testapp.ui.splash.SplashViewModel.fetchSensorLocations(SplashViewModel.java:25)
    at com.testapp.testapp.ui.splash.SplashViewModel.observeLoadStatus(SplashViewModel.java:17)
    at com.testapp.testapp.ui.splash.SplashActivity.observeViewModels(SplashActivity.java:121)
    at com.testapp.testapp.ui.splash.SplashActivity.onResume(SplashActivity.java:77)
    at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1355)
    at android.app.Activity.performResume(Activity.java:7138)

I will really appreciate an explanation and why I keep getting this error.

Thanks

like image 591
cr05s19xx Avatar asked Jan 06 '19 16:01

cr05s19xx


1 Answers

You are setting up 2 observers on the same source of the MediatorLiveData.

You can only set 1 observer per source, otherwise an IllegalStateException is thrown, just like in your case.

Move your observe method from onResume() to onCreate().

A ViewModel does not get destroyed when you are putting your activity in the background. It is still in memory, waiting for the activity to return to the foreground. It only gets destroyed when the activity is completely closed.

If you want to stop observing a particular source, simply use removeSource().

If you want to start observing the source again, use addSource().

like image 130
Daniel B. Avatar answered Oct 13 '22 00:10

Daniel B.