Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can we "assign" LiveData from Room to MutableLiveData, within ViewModel

Recently, I am stuck with the following code.

public class NoteViewModel extends ViewModel {

    private final MutableLiveData<List<Note>> notesLiveData = new MutableLiveData<>();

    public NoteViewModel() {
        LiveData<List<Note>> notesLiveDataFromRepository = NoteRepository.INSTANCE.getNotes();

        // How can I "assign" LiveData from Room, to MutableLiveData?
    }

}

I was wondering, how can I "assign" LiveData from Room, to MutableLiveData?

Using Transformation.map and Transformation.switchMap wouldn't work, as both returns LiveData, not MutableLiveData.


Possible workaround

One of the possible solution is that, instead of

@Dao
public abstract class NoteDao {
    @Transaction
    @Query("SELECT * FROM plain_note")
    public abstract LiveData<List<Note>> getNotes();

I will use

@Dao
public abstract class NoteDao {
    @Transaction
    @Query("SELECT * FROM plain_note")
    public abstract List<Note> getNotes();

Then, in my ViewModel, I will write

public class NoteViewModel extends ViewModel {
    private final MutableLiveData<List<Note>> notesLiveData = new MutableLiveData<>();

    public NoteViewModel() {
        new Thread(() -> {
            List<Note> notesLiveDataFromRepository = NoteRepository.INSTANCE.getNotes();
            notesLiveData.postValue(notesLiveDataFromRepository);
        }).start();
    }

}

I don't really like this approach, as I'm forced to handle threading thingy explicitly.

Is there a better way, to avoid handling threading explicitly?

like image 280
Cheok Yan Cheng Avatar asked Apr 01 '18 21:04

Cheok Yan Cheng


People also ask

How can data be stored in LiveData in a ViewModel?

Use LiveData for the app's data (word, word count and the score) in the Unscramble app. Add observer methods that get notified when the data changes, update the scrambled word text view automatically. Write binding expressions in the layout file, which are triggered when the underlying LiveData is changed.

What is the difference between LiveData and MutableLiveData?

By using LiveData we can only observe the data and cannot set the data. MutableLiveData is mutable and is a subclass of LiveData. In MutableLiveData we can observe and set the values using postValue() and setValue() methods (the former being thread-safe) so that we can dispatch values to any live or active observers.

Is MutableLiveData thread-safe?

So, LiveData is immutable. MutableLiveData is LiveData which is mutable & thread-safe.


1 Answers

The trick is to not do any of the actual fetching in the view model.

Getting data, be it from network or database, should be done in the repository. The ViewModel should be agnostic in this regard.

In the ViewModel, use the LiveData class, not MutableLiveData. Unless you really find a use case for it.

// In your constructor, no extra thread
notesLiveData = notesLiveDataFromRepository.getAllNotes();

Then in your repository you can have the logic in the getAllNotes() method for determining where those notes are coming from. In the repository you have the MutableLiveData. You can then postValue to that, from a thread that is getting the data. That isn't necessary for room though, that is handled for you.

So in your repository you would have another LiveData being returned that is backed directly from a DAO method.

In that case, you need to stick with public abstract LiveData<List<Note>> getNotes();.

Activity

public class MyActivity extends AppCompatActivity {

    private MyViewModel viewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Set up your view model
        viewModel = ViewModelProviders.of(this).get(MyViewModel.class);

        // Observe the view model
        viewModel.getMyLiveData().observe(this, s -> {
            // You work with the data provided through the view model here.
            // You should only really be delivering UI updates at this point. Updating
            // a RecyclerView for example.
            Log.v("LIVEDATA", "The livedata changed: "+s);
        });

        // This will start the off-the-UI-thread work that we want to perform.
        MyRepository.getInstance().doSomeStuff();
    }
}

ViewModel

public class MyViewModel extends AndroidViewModel {

    @NonNull
    private MyRepository repo = MyRepository.getInstance();

    @NonNull
    private LiveData<String> myLiveData;

    public MyViewModel(@NonNull Application application) {
        super(application);
        // The local live data needs to reference the repository live data
        myLiveData = repo.getMyLiveData();
    }

    @NonNull
    public LiveData<String> getMyLiveData() {
        return myLiveData;
    }
}

Repository

public class MyRepository {

    private static MyRepository instance;

    // Note the use of MutableLiveData, this allows changes to be made
    @NonNull
    private MutableLiveData<String> myLiveData = new MutableLiveData<>();

    public static MyRepository getInstance() {
        if(instance == null) {
            synchronized (MyRepository.class) {
                if(instance == null) {
                    instance = new MyRepository();
                }
            }
        }
        return instance;
    }

    // The getter upcasts to LiveData, this ensures that only the repository can cause a change
    @NonNull
    public LiveData<String> getMyLiveData() {
        return myLiveData;
    }

    // This method runs some work for 3 seconds. It then posts a status update to the live data.
    // This would effectively be the "doInBackground" method from AsyncTask.
    public void doSomeStuff() {
        new Thread(() -> {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException ignored) {
            }

            myLiveData.postValue("Updated time: "+System.currentTimeMillis());
        }).start();
    }

}
like image 71
Knossos Avatar answered Oct 20 '22 21:10

Knossos