Performing network operations from ViewModel - android

I am using ViewModel, introduced in IO/17.
I am using following guidelines provided on android developers page.
https://developer.android.com/topic/libraries/architecture/viewmodel.html
Following is their sample code.
public class MyViewModel extends ViewModel {
private MutableLiveData<List<User>> users;
public LiveData<List<User>> getUsers() {
if (users == null) {
users = new MutableLiveData<List<Users>>();
loadUsers();
}
return users;
}
private void loadUsers() {
// do async operation to fetch users
}
}
I wish to perform Volley request in the 'loadUsers()' method. But I cannot do it as it needs a 'context' as follows
Volley.newRequestQueue(context).add(jsonObjectRequest);
So my question is,
Is it recommended(or possible) to perform network operations inside a ViewModel??
If yes(if possible), how to do it?

You could use the AndroidViewModel class instead of ViewModel. AndroidViewModel holds a reference to the application context.
https://youtu.be/5qlIPTDE274

Consider Dagger, that way you don't have to worry, about providing context for Volley from your ViewModel.

AndroidViewModel is subclass of ViewModel. The Difference between them is we can pass Application Context which can be used whenever Application Context is required for example to instantiate Database in Repository.
AndroidViewModel is a Application context aware ViewModel.You must use AndroidViewModel for Application Context.
public class MyViewModel extends AndroidViewModel {
private MutableLiveData<List<User>> users;
private Application application;
public MyViewModel(#NonNull Application application) {
this.application=application;
super(application);
}
public LiveData<List<User>> getUsers() {
if (users == null) {
users = new MutableLiveData<List<Users>>();
loadUsers();
}
return users;
}
private void loadUsers() {
// Pass Context to Repository
}}
You should never store a reference of activity or a view that references a activity in the ViewModel.Because ViewModel is designed to outlive a activity and it will cause Memory Leak.
Is it recommended(or possible) to perform network operations inside a
ViewModel??
No, you should not perform Networking Operations inside a ViewModel.
If yes(if possible), how to do it?
Get Application Context by using AndroidModelView and pass it to Repository,
as recommended by Android Team.

Related

Android Room database ViewModel does not reflect synchronously inserted data

I am having an issue where data that is written to my Room database does not appear in a ViewModel even though I am writing it synchronously.
This is what a log would look like:
com.widget D/WriteActivity: Writing widget data to the database
com.widget D/WriteActivity: Starting the ReadActivity
com.widget D/ReadActivity: Got a new list of 0 objects
Here is the situation:
I have two activities, WriteActivity and ReadActivity. Inside of the ReadActivity I have a ViewModel listening for database changes (that is instantiated in the onCreate method of the Activity):
// observe the widget data
WidgetViewModel widgetViewModel = ViewModelProviders.of(this).get(
WidgetViewModel.class);
widgetViewModel.getAllWidgets().observe(this, new Observer<List<Widget>>() {
#Override
public void onChanged(#Nullable final List<Widget> updatedWidgets) {
Log.d(TAG, "Got a new list of " + updatedWidgets.size() + " objects");
}
});
Inside of the WriteActivity I have code that adds an object to the database on a background thread, then, once it completes, it launches the ReadActivity:
// persist the objects to the room database (doInBackground)
WidgetRepository myObjectRepository = new WidgetRepository(getApplication());
myObjectRepository.insert(myObjects); // myObjects is a list of 5 objects
// load the ReadActivity (onPostExecute)
Intent myIntent = new Intent(WriteActivity.this, ReadActivity.class);
WriteActivity.this.startActivity(myIntent);
Here is my DAO:
#Dao
public interface WidgetDao {
#Query("SELECT * FROM widget_table")
LiveData<List<Widget>> getAll();
#Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(Widget... widgets);
}
My Database:
#Database(entities = {Widget.class}, version = 1, exportSchema = false)
public abstract class WidgetDatabase extends RoomDatabase {
public abstract WidgetDao widgetDao();
private static volatile WidgetDatabase INSTANCE;
static WidgetDatabase getDatabase(final Context context) {
if (null == INSTANCE) {
synchronized (WidgetDatabase.class) {
if (null == INSTANCE) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
WidgetDatabase.class, "widget_database")
.build();
}
}
}
return INSTANCE;
}
}
My repository:
public class WidgetRepository {
private final WidgetDao widgetDao;
private final LiveData<List<Widget>> widgets;
public WidgetRepository(Application application) {
WidgetDatabase db = WidgetDatabase.getDatabase(application);
widgetDao = db.widgetDao();
widgets = widgetDao.getAll();
}
public LiveData<List<Widget>> getWidgets() {
return widgets;
}
public void insert(List<Widget> widgetsToInsert) {
widgetDao.insert(widgetsToInsert.toArray(
new Widget[widgetsToInsert.size()]));
}
My ViewModel:
public class WidgetViewModel extends AndroidViewModel {
private final LiveData<List<Widget>> widgets;
public WidgetViewModel (Application application) {
super(application);
WidgetRepository widgetRepository = new WidgetRepository(application);
widgets = widgetRepository.getWidgets();
}
public LiveData<List<Widget>> getAllWidgets() { return widgets;
}
}
Your issue is that LiveData<List<Widget>> is not being notified.
So, how to update that ?
see below,
Update LiveData objects
LiveData has no publicly available methods to update the stored data.
The MutableLiveData class exposes the setValue(T) and postValue(T)
methods publicly and you must use these if you need to edit the value
stored in a LiveData object. Usually MutableLiveData is used in the
ViewModel and then the ViewModel only exposes immutable LiveData
objects to the observers.
So, changes you can make to your ViewModel:
public class WidgetViewModel extends AndroidViewModel {
private final MutableLiveData<List<Widget>> widgets = new MutableLiveData<List<Widget>>(); // Make it mutable livedata
public WidgetViewModel (Application application) {
super(application);
WidgetRepository widgetRepository = new WidgetRepository(application);
//widgets = widgetRepository.getWidgets();
//use this to update your live data instead,
widgets.setValue(widgetRepository.getWidgets().getValue()); // This will update your live data, use like this for future updates.
}
public LiveData<List<Widget>> getAllWidgets() { return widgets;
}
}
Checkout more from here
I figured out what was happening. It's embarassing... when I asked my question I had a fundamental misunderstanding of how LiveData works. RTFM jeez :)
After I read the documentation I came to a stunning revelation: LiveData is tied to the lifecycle of the activity. In the example I gave I was attempting to access the ViewModel during the onResume of my ReadActivity because I was wanted to make sure that the UI updated properly. Rookie mistake. Like a fool I believed that the Observer callback would only fire when the data encapsulated by the LiveData was modified. In reality, the LiveData callback in the Observer is called when the activity becomes active regardless of whether the data has changed, so there is no need to try to do anything in the onResume lifecycle method manually. Just wait for the onChanged callback of the Observer and update the UI at that time.
App is working great now, thank you to everyone who read my question.

Android ViewModel: Should I "borrow" the observe() method from LiveData like in the official example?

When working with ViewModels the View observes the ViewModel. It has to register as an observer. In the official tutorial of Google this registration is delegated to the observe() method of a LiveData object.
public class MyViewModel extends ViewModel {
private MutableLiveData<List<User>> users;
public LiveData<List<User>> getUsers() {
if (users == null) {
users = new MutableLiveData<List<Users>>();
loadUsers();
}
return users;
}
private void loadUsers() {
// Do an asynchronous operation to fetch users.
}
}
public class MyActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
// Create a ViewModel the first time the system calls an activity's onCreate() method.
// Re-created activities receive the same MyViewModel instance created by the first activity.
MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
model.getUsers().observe(this, users -> {
// update UI
});
}
}
The method getUsers() returns the LiveData object itself. It's observe() method is used to register the observer. The View does not observe the the ViewModel but a part of it's implementation.
Now is this best practice, when working with ViewModels not to observe themselves but parts of their implementation in form of LiveData objects? Or is this an introduction of low quality?
Based on the answer of Chris I give my own answer. I think the tutorial is not best practice for the simple reason, that an object should not expose it's internal implementation. Base on the argumentation of Chris I was looking for an option to get encapsulation without losing the named features. The result is the method observerUsers() which delegates to a LiveData object internally.
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MainActivityViewModel model = ViewModelProviders.of(this).get(MainActivityViewModel.class);
model.observeUsers(this,
new Observer<List<User>>() {
#Override
public void onChanged(#Nullable List<User> users) {
updateUI();
}
}
);
}
void updateUI() {
}
static class MainActivityViewModel extends ViewModel {
private MutableLiveData<List<User>> users;
public void observeUsers(#NonNull LifecycleOwner owner,
#NonNull Observer<List<User>> observer) {
getUsers().observe(owner, observer);
}
private LiveData<List<User>> getUsers() {
if (users == null) {
users = new MutableLiveData<>();
loadUsers();
}
return users;
}
private void loadUsers() {
// Do an asynchronous operation to fetch users.
}
}
static class User {
}
}
Still List<User> exposes internal implementation. It could be improved to a class Users.
I put everything into one file and use inner static class. This is not meant as best practice. It was just to be able to quickly edit everything within one file. Especially the model User belongs into it's very own file, while I often put the ViewModel into the View class it belongs to like this.
My second point of critic matches the case that the ViewModel itself observes an underlying model. In this case the observer method onChange() is very general and requires a very general update method like updateUI(). You may want to observe more specific events of the model to do specific updates.
I'd say yes it's best practice for the ViewModel to expose its data through some form of Observable, whether that be LiveData or something like an RX Observable.
This breaks from other architectures such as MVP where the presenter would typically have a reference to the View which gets called when something changes. The guidelines are quite specific about what a ViewModel should reference.
A ViewModel must never reference a view, Lifecycle, or any class that may hold a reference to the activity context.
By exposing your data as an Observable through the ViewModel this means views can come and go, and once subscribed will receive the latest data and any subsequent updates. Again the guidelines have some detail.
If the activity is re-created, it receives the same MyViewModel instance that was created by the first activity. When the owner activity is finished, the framework calls the ViewModel objects's onCleared() method so that it can clean up resources
https://developer.android.com/topic/libraries/architecture/viewmodel.html

Making changes to LiveData to "redo" work in ViewModel

So, I have just started experimenting with LiveData - I am busy with a new project, where I am using ViewModel as well as LiveData - with some of the RESTFul services I use to fetch data, they take no parameters and return some data.
A typical setup of the MVVM paradigm with LiveData looks much like this:
public class MyActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
model.getUsers().observe(this, users -> {
// update UI
});
}
}
Now when we leave this activity, and go to a new activity, by using an Intent or some other means, and not pressing the back button (So, finalize is not called) - and then come back to MyActivity - we of course don't fetch the users again, as we should still have that data.
However, what if we did want to fetch them again?
The only way to do this properly, from what I have looked at, seems to call "setValue" on the getUsers() LiveData object
Something like this:
public class MyActivity extends AppCompatActivity {
public void onResume() {
viewModel.setActive(true);
}
}
And the ViewModel would look like this:
private final MutableLiveData<Boolean> activeLiveData = new MutableLiveData<>();
ViewModel(ViewModelRepo repo){
this.repo = repo;
results = Transformations.switchMap(activeLiveData, active ->{
if(active){
return repo.getUsers();
}else {
return AbsentLiveData.create(); //"Null live data"
}
});
}
LiveData<Users>> getUsers() {
return results;
}
//This could be called "update" with no params
void setActive(boolean active) {
activeLiveData.setValue(active);
}
The one reason I have decided to do it like this is because Google does not want us doing this:
class MyViewModel extends ViewModel {
private final PostalCodeRepository repository;
public MyViewModel(PostalCodeRepository repository) {
this.repository = repository;
}
private LiveData<String> getPostalCode(String address) {
// DON'T DO THIS
return repository.getPostCode(address);
}
}
For this reason:
If this is the implementation, the UI would need to unregister from
the previous LiveData and re-register to the new instance each time
they call getPostalCode(). Moreover, if the UI is re-created, it
triggers another call to repository.getPostCode() instead of using the
previous call’s result.
Is there a better way to get the ViewModel to "redo" its repo.getUsers() call? Perhaps I could just make a method that says "Update()" instead of "active" but still - its doing the same thing differently.
Well here you're doing the fetching in the creator of the ViewModel, which locks things in place. Usually they'd advise to fetch the data in the getter, if the data is not there already.
So a good option would be to use the regular pattern first :
private MutableLiveData<Users> users = null;
ViewModel(ViewModelRepo repo){
this.repo = repo;
}
LiveData<Users> getUsers() {
if (users = null) {
fetchUsers();
}
return users;
}
public void fetchUsers() {
users.postValue(repo.getUsers());
}
And then from your Activity/Fragment, whenever you feel necessary to "refresh the users", you'd simply call viewModel.fetchUsers();

android retrofit2, dagger2 unit test

I learn how to test the presenter layer of MVP architecture in android, my presenter using retrofit 2 and in my activity I used dagger 2 as dependency injection to my presenter, this is my Dagger and presenter injection looks like:
#Inject
AddScreenPresenter addScreenPresenter;
This is the Dagger builder :
DaggerAddScreenComponent.builder()
.netComponent(((App) getApplicationContext()).getNetComponent())
.addScreenModule(new AddScreenModule(this, new ContactDatabaseHelper(this)))
.build().inject(this);
and this is my presenter constructor :
#Inject
public AddScreenPresenter(Retrofit retrofit, AddScreenContact.View view, ContactDatabaseHelper contactDatabaseHelper)
{
this.retrofit = retrofit;
this.view = view;
this.contactDatabaseHelper = contactDatabaseHelper;
}
I have write the unit test class and mock the Retrofit class, but when I run it, the error appears :
Mockito cannot mock/spy following:
- final classes
- anonymous classes
- primitive types
This is the test class :
#RunWith(MockitoJUnitRunner.class)
public class AddScreenPresenterTest {
private AddScreenPresenter mAddPresenter;
#Mock
private Retrofit mRetrofit;
#Mock
private Context mContext;
#Mock
private AddScreenContact.View mView;
#Mock
private ContactDatabaseHelper mContactDatabaseHelper;
String firstName, phoneNumber;
Upload upload;
#Before
public void setup() {
mAddPresenter = new AddScreenPresenter(mRetrofit, mView, mContactDatabaseHelper);
firstName = "aFirstName";
phoneNumber = "998012341234";
Uri path = Uri.parse("android.resource://"+BuildConfig.APPLICATION_ID+"/" + R.drawable.missing);
upload = new Upload();
upload.title = firstName;
upload.description = "aDescription";
upload.albumId = "XXXXX";
upload.image = new File(path.getPath());
}
#Test
public void checkValidationTest() {
verify(mAddPresenter).checkValidation(firstName, phoneNumber);
}
#Test
public void uploadMultiPartTest() {
verify(mAddPresenter).uploadMultiPart(upload);
}
}
this is my module :
#Module
public class AddScreenModule {
private final AddScreenContact.View mView;
private final ContactDatabaseHelper mContactDatabaseHelper;
public AddScreenModule (AddScreenContact.View view, ContactDatabaseHelper contactDatabaseHelper)
{
this.mView = view;
this.mContactDatabaseHelper = contactDatabaseHelper;
}
#Provides
#CustomScope
AddScreenContact.View providesAddScreenContactView() {
return mView;
}
#Provides
#CustomScope
ContactDatabaseHelper providesContactDatabaseHelper() {
return mContactDatabaseHelper;
}
}
I know that Retrofit class is a final class, and now I stuck and don't know how to create the presenter object in my test class. Please help me, how to create the object of the presenter class with retrofit in the constructor. Feel free to ask if my question is not clear enough, and thank you very much for your help.
Personally I'd make the presenter not depend on the Retrofit class but rather on the services created by Retrofit - These are mockable.
It's hard to say from the code you posted which services your presenter actually uses, but for the sake of simplicity let's say it uses only one and let's say it's AddsService - This is an interface ready to work with Retrofit. Something like this for example
public interface AddsService {
#GET(...)
Call<List<Adds>> getAllAdds();
}
Now you can make your presenter depend on this rather than Retrofit
#Inject
public AddScreenPresenter(AddsService addsService,
AddScreenContact.View view,
ContactDatabaseHelper contactDatabaseHelper){
this.addsService = addsService;
this.view = view;
this.contactDatabaseHelper = contactDatabaseHelper;
}
You now need to provide this dependency. I'm guessing you have also a NetModule since you have a NetComponent, so I assume you can just do:
#Module
public class NetModule {
// Methods providing Retrofit
#Provides
#Singleton
public AddsService providesAddsService(Retrofit retrofit) {
return retrofit.create(AddsService.class);
}
}
Notice how the providesAddsService depends on retrofit? This should be already provided since your presenter is depending on it. You shouldn't need to change anything for that. Dagger is able to figure out how to provide Retrofit to the method providesAddsService.
Please notice also that I'm assuming you can provide these in a Singleton scope. I assume this because in your code you retrieve the component from the application, which should handle the singleton scope.
Now in your tests you can simply mock AddsService and test your presenter.
If your presenter depends on more services, I'd also pass them in the constructor and provide the implementations with Dagger.
As a bonus, let me also say that the retrofit instance and the retrofit services should only be created once (or at least as less times as possible). This is because they're usually expensive operations and you usually always query the same endpoints with different parameters.
EDIT
To answer some of the questions in the comments. First the easy one: How to create the presenter in the test classes? Like you I too try to get away from Dagger during tests, that's why I prefer constructor dependency injection just like you show you're using. So in my test class I'd have something very similar like you:
#RunWith(MockitoJUnitRunner.class)
public class AddScreenPresenterTest {
private AddScreenPresenter mAddPresenter;
#Mock
private AddsService addsService;
// ...
#Before
public void setUp() throws Exception {
mAddPresenter = new AddScreenPresenter(addsService,
mView, mContactDatabaseHelper);
// ...
}
}
So basically the only difference is that I would pass the mock to the service.
Now the second question: How to call the presenter constructor from the activity? Well you don't... that's the whole idea of dependency injection. You should use dagger to provide your presenter. I think this is already what you do and I guess this is what it's in your activity:
#Inject
AddScreenPresenter addScreenPresenter;
So all you need to do is have a provider method in your module that provides this and is able to inject it.
You can also make the component return the presenter provided by the module:
#Component(...)
public interface AddScreenComponent {
AddScreenPresenter getPresenter();
}
And then in your activity you'd do something like:
addScreenPresenter = component.getPresenter();
I don't really have any preference here. The key point is to understand that you should not build the objects yourself (unless inside #Modules). As a rule of thumb any time you see new being used that means you have a tight dependency on that object and you should extract it to be injected. So this is why you should avoid creating the presenter inside your activity. It will couple the presenter to the activity.

In Dagger are Singletons within the sub-graph cached or will they always be recreated when a new activity sub-graph is constructed?

I'm using Dagger to create activity specific object graphs. Within this subgraph, I make use of a Singleton MyPresentationModel.
When i exit my activity, and enter the activity again, my expectation is that a new instance of the activity specific object graph is created, which in turn would create a new instance of Singleton MyPresentationModel (by virtue of the #Singleton semantic per Dagger. See this So answer for specifics) which would then last for the life of the activity specific object graph.
However, this is not what i'm observing, every time the activity specific object graph is created, the same instance of MyPresentationModel is used. I added a debug point into the constructor of MyPresentationModel. The very first time we enter the constructor. Subsequently even on activity exits and reentries, we don't enter the constructor (and because of this the UserSession being used within my Presentation model uses the old value from the very first constructor injection).
While i can technically solve the problem by re-setting UserSession inside MyPresentaitonModel with an external public setter, I want to understand better the mechanics of the activity specific object graph creation/destruction.
By nullifying the graph in my onDestroy, does that still mean that there is a possibility of the Singletons within my subgraph being reused at a later point ? (possibly until they are truly GCed?)
Here's some code:
// MyAppModule
#Module(
includes = { UserSession.class},
injects = { MyApplication.class })
public class MyAppModule {
private final MyApplication _app;
MyAppModule(MyApplication app) {
_app = app;
}
// ...
}
// Main Activity
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
_activityObjectGraph = MyApplication.get()
.getObjectGraph()
.plus(Arrays.<Object>asList(new SubModule()).toArray());
// Inject ourselves so subclasses will have dependencies fulfilled when this method returns.
_activityObjectGraph.inject(this);
}
#Override
protected void onDestroy() {
_activityObjectGraph = null;
// this eagerly allows GC, but doesn't necessarily destroy the subgraph ?
super.onDestroy();
}
// SubModule
#Module(injects = { MyPresentationModel.class, MainActivity.class },
addsTo = MyAppModule.class,
library = true)
public class SubModule {}
}
// MyPresentationModel
#Singleton
public class MyPresentationModel {
private UserSession _session;
#Inject
public MyPresentationModel(UserSession session) {
_session = session;
}
public void someMethodThatUsesSessionInfo() {
// _session.getUser() ...
}
}
#weefbellington posted a very informative answer, but reading it made me realize my question was not specific and clear enough. Here's attempt 2:
MyAppModule (main graph) -> provides a Singleton UserSession
MySubModule (sub graph plused onto MyAppModule) -> provides "activity specific" Singleton MyPresentationModel which requires a UserSession (provided my MyAppModule) on construction.
I now close the activity, destroying MySubModule (and also hopefully MyPresentationModel which is a Singleton), I update UserSession with some new information.
I open MainActivity again, thus re-creating the sub-graph from MySubModule, which inturn provides a MyPresentationModel.
The issue I'm noticing is that MyPresentationModel which is the local Singleton is not being reconstructed again i.e. this part of the code:
#Inject
public MyPresentationModel(UserSession session) {
_session = session;
}
is only ever being called once. My expectation was that this part of the code would be run again, and the UserSession would be pulled again from the Main graph and since it was updated, it would hold the updated values. My question is: are Singletons within the sub-graph cached in anyway or will they always be recreated when a new activity sub-graph is spawned?
How MyPresentationModule is injected depends on how your modules are specified. For example, assume that you are injecting the class Foo:
public class Foo {
private final MyPresentationModel model;
#Inject
public Foo(MyPresentationModel model) {
this.model = model;
}
}
If your modules are structured like (A), then the MyPresentationModel singleton will be injected into Foo by the main object graph:
EXAMPLE A
#Module(injects = { Foo.class })
public class MainModule { ... }
#Module(addsTo = MainModule.class, injects = { MyPresentationModel.class })
public class SubModule { ...}
Alternatively, if your modules are structured like (B), then the MyPresentationModel singleton will be injected into Foo by the subgraph:
EXAMPLE B
#Module
public class MainModule { ... }
#Module(addsTo = MainModule.class, injects = { Foo.class, MyPresentationModel.class })
public class SubModule { ... }
In your particular case, since you have specified that MyAppModule injects MyApplication, I would guess that you are trying to inject MyPresentationModel into your Application class. This is probably not what you want to do. You probably want inject this into your Activity class using the submodule, as in (C).
EXAMPLE C
#Module(injects = { MainActivity.class, MyPresentationModel.class },
addsTo = MyAppModule.class,
library = true)
public class SubModule { ... }
public class MainActivity {
#Inject MyPresentationModel presentationModel;
...
}
If you do this the MyPresentationModel singleton will be bound to the Activity subgraph instead of the main graph, and should be disposed when the Activity is destroyed.
Once you have a handle on Dagger, you might want to check out Mortar, which gives you finer-grained control over creation and destruction of ObjectGraph subscopes.

Categories

Resources