Instantiate ViewModels directly, without making use of ViewModelProviders.of method - android

I have a ViewModel called RecipesViewModel. Usually, I instantiated it this way:
RecipesViewModel viewModel = ViewModelProviders.of(this, new ViewModelProvider.Factory() {
#Override
public <T extends ViewModel> T create(Class<T> modelClass) {
return (T) new RecipesViewModel(recipesRepository);
}
}).get(RecipesViewModel.class);
But now I'm using dagger2 and so I put a #Inject annotation on the constructor of this ViewModel, so I'm able to inject it directly in my fragment, using field injector.
My question is: do I lose something starting the viewmodel this way instead of ViewModelProviders.of way? My ViewModel is already Scoped, so only one instance is create in context.
Other option is to move only the factory instantiation to a dagger2 module, but if there is no problem I prefer the first aproach.
-- EDIT --
Reading the documentation android.arch.lifecycle.ViewModel, I'm a little more afraid. Whe use ViewModelProviders.of to provide a Scope (fragment or activity). If I instantiate it directly what will be the Scope?
ViewModel is a class that is responsible for preparing and managing
the data for an Activity or a Fragment. It also handles the
communication of the Activity / Fragment with the rest of the
application (e.g. calling the business logic classes).
A ViewModel is always created in association with a scope (an fragment
or an activity) and will be retained as long as the scope is alive.
E.g. if it is an Activity, until it is finished.
In other words, this means that a ViewModel will not be destroyed if
its owner is destroyed for a configuration change (e.g. rotation). The
new instance of the owner will just re-connected to the existing
ViewModel.
-- /EDIT --
The RecipesViewModel code is showing below:
#PerActivity
public class RecipesViewModel extends ViewModel {
private static final String TAG = "RecipesViewModel";
private final RecipesRepository recipesRepository;
private LiveData<List<Recipe>> recipes = null;
#Inject
public RecipesViewModel(RecipesRepository recipesRepository) {
this.recipesRepository = recipesRepository;
}
public final void loadAll() {
recipes = recipesRepository.getRecipes();
}
public LiveData<List<Recipe>> getRecipes() {
return recipes;
}
}

For me right now (and I need to research this), but injecting a view model instead of using the ViewModelProviders functionality means you lose some easy activity-fragment communication.
For example from the docs they provide an example of an activity hosting 2 fragments. If one fragment needs to talk to another, the previous method was to maintain an interface via the activity who also had to take care of the lifecycle of that interface. Instead now you can just fetch it from the the ViewModelProviders 'repo' whenever you need.

Related

How to pass complex, non serializable object to android fragments

Hello fellow Android developers,
I wanna know how do you guys pass complex non serializable (& non parcelable) object to fragments. (such as Listener, Api client, ...)
Let me explain my use case:
The use case
I'm building an Android application composed of one "host" activity and 3 fragments.
Currently I'm passing the object using a custom constructor on the fragment (bad practice I know).
The fragments constructors looks like the following:
/**
* Do not remove ever or you'll face RuntimeException
*/
public FirstFragment() {
}
public FirstFragment(Session session,
ApiClient apiClient,
FirebaseAnalytics firebaseAnalytics) {
mSession = session;
mApiClient = apiClient;
mFirebaseAnalytics = firebaseAnalytics;
}
And I'm using them in the host activity like this
private FirstFragment getFirstFragment() {
if (mFirstFragment == null) {
mFirstFragment = new FirstFragment(mSession, mApiClient, mFirebaseAnalytics);
}
return mHomeFragment;
}
[...]
private void loadFragment(Fragment fragment, String tag) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.frame_container, fragment, tag);
transaction.commit();
}
[...]
private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
= new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch (item.getItemId()) {
case FIRST_FRAGMENT_RES_ID:
toolbar.setTitle(R.string.first_fragment_title);
loadFragment(getFirstFragment(), "first_fragment");
return true;
[...]
}
return false;
}
};
This solution works well almost all the time. But sometimes (and I don't know when exactly) the default constructor is invoked and therefore all local members are null.
Possible solutions
To solve the problem I'm thinking about the following solutions:
Singletons, singletons everywhere
Most of the objects I'm passing are singletons therefore I can access them in the default constructor of the fragments:
public FirstFragment() {
mSession = Session.getInstance(getContext());
mApiClient = ApiClient.getInstance(getContext());
mFirebaseAnalytics = FirebaseAnalytics.getInstance(getContext());
}
Problems
However the above solution wouldn't work if I need to pass a callback or something. How can it be done like this then?
Access the objects using parent activity
I think it's one of the ugliest possible solutions because it will couple the Fragments to the parent activity. The idea is something like this
public FirstFragment() {
mSession = Session.getInstance(getContext());
mApiClient = ApiClient.getInstance(getContext());
mFirebaseAnalytics = FirebaseAnalytics.getInstance(getContext());
mListener = (Listener) getActivity(); // <- will works because parent activity implement the interface
}
Using broadcast & receiver
The idea is to keep passing singleton everywhere and use broadcast & receiver instead of listener.
How do you guys managed this scenario?
Thanks in advance !
You probably want to look into dependency injection (using a tool like Dagger or alternatives), especially for objects like an Api Client. Post the setup, you'd define, just once, how an Api Client instance could be constructed. And later you can use it pretty much everywhere with a one-line statement. The instance is guaranteed to be available upon the fragment instantiation. Further reading: https://dagger.dev/tutorial/
According to your use case, it might be easier to use a ViewModel and store your objects there. Your ViewModel will be shared across your fragments and your host
activity.
See https://developer.android.com/topic/libraries/architecture/viewmodel
Have you considered using "Shared" ViewModel?
Essentially, a sub-class of ViewModel (which is class designed to store and manage UI-related data in a lifecycle conscious way for activities and fragments) can be created like below,
class SharedViewModel : ViewModel()
Inside this class you can have your custom objects with their correct state
Next, in your 1st Fragment you can obtain a handle to this SharedViewmodel like below,
class MasterFragment : Fragment() {
private lateinit var model: SharedViewModel
And obtain the handle to it using below code,
model = activity?.run {
ViewModelProviders.of(this)[SharedViewModel::class.java]
}
You can write your own logic/method/flow inside SharedViewModel to manipulate any custom object's states.
And once all this is done, In your 2nd Fragment, you can create the handle to SharedViewModel similar to above code and using SharedViewModel object you can retrieve the "modified" custom object from same SharedViewModel
It's been several months and I have now come up with a different solution.
For the UI related data
For the UI related stuff I'm now using the androidx livedata
For the complex non serializable data
My use case was to pass complex object to the fragment, such as manager, parent activity (trough a listener), etc... The approach I have taken is by injecting these data manually from the parent activity.
The first things to do was to remove the objects from the fragment constructor and use the default constructor instead, so that I won't face any instantiation errors.
Then I have created an inject() method on the fragment classes that look like this:
public void inject(BillingManager billingManager, Listener listener) {
mBillingManager = billingManager;
mListener = listener;
}
Each fragment will have their own inject method width the objects that should be injected as parameters.
In the parent activity I have override the onAttachFragment() method to handle the fragment attach process:
#Override
public void onAttachFragment(#NonNull Fragment fragment) {
super.onAttachFragment(fragment);
if (fragment.getClass().equals(FirstFragment.class)) {
((FirstFragment) fragment).inject(mBillingManager, this);
} else if (fragment.getClass().equals(HomeFragment.class)) {
((HomeFragment) fragment).inject(this);
}
}
Simple, and now everything work great.

Applying MVVM between ViewModel and Fragment/Activity interactions and communication

I am investing a lot of time into properly understanding of architecture components and the how everything fits into MVVM pattern. So far here's how I see things (without doing Dagger injection as I haven't got that far yet):
a) UserEntity is a class with #Entity annotation to handle Room's table creation
#Entity(tableName="users")
public class Users{
private long id;
private String name;
}
b) User pojo class in the model to use it around the app and has supplemental fields as needed.
public class User{
private long id;
private String name;
private List<Role> roles;
private Preferences preferences;
}
beside this there can be different pojos depending on what I need from db, for instance, UserWithRoles
c) UserDao takes care of getting or inserting/updating info in the room database. In here, for #Insert, #Update I can user the UserEntity but for #Query I can also use the pojo classes
#Dao
public abstract class UserDao{
#Insert
public abstract long insertUser(User user)
#Query("Select someFields from inner joined tables")
public abstract LiveData<List<UserRoles> getUsersWithRoles();
}
d) Have RepositoryUser as the repository between ViewModel and Dao
public class RepositoryUser{
private UserDao userDao;
public RepositoryUser(Application app){
Database db = Databaase.getDatabase(app.getApplicationContext);
userDao = db.userDao();
}
public LiveData<List<UserWithRoles>> getUsersWithRoles(){
return userDao.getUsersWithRoles()
}
}
e) UserWithRolesViewModel to be available for the fragment that shows the list with users and their roles
public class UserWithRolesViewModel extends AndroidViewModel{
private RepositoryUser repositoryUser;
public UserWithRolesViewModel(Application app){
super(app);
repositoryUser = new RepositoryUser(app);
}
public LiveData<List<UserWithRoles>> getUsersWithRoles(){
return repositoryUser.getUsersWithRoles()
}
}
f) In my fragment I can do something like:
public void onCreate(...){
viewModel = ViewModelProviders.of(this).get(UserWithRolesViewModel.class);
}
public View onCreateView(...){
viewModel.getUsersWithRoles().observe(...)
public void onChanged(...){
adapter.setData(...);
}
}
However, there are some pieces that are missing. From my understanding according to MVVM the view should only be responsible for showing info, so no actual logic or even handling to be made inside the fragment or activity. At this point I have 2 questions:
On the regular way, I would create an interface, for instance onFragmentAction and implement it in activity. Then on fragment when I wanted to inform the activity to do something, I would do callback.onFragmentAction(params) and the onFragmentAction in the activity would fire and act accordingly. How is this scenario handled in MVVM? How does a fragment talk to it's parent activity?
On the regular way I would have inside the fragment's onCreateView, inflate the layout, use findViewById to get the views and use, for instance textView.setText() or button.setOnClickListener(). How can this be done in MVVM? Use DataBinding?
On the regular way, I would create an interface, for instance
onFragmentAction and implement it in activity. Then on fragment when I
wanted to inform the activity to do something, I would do
callback.onFragmentAction(params) and the onFragmentAction in the
activity would fire and act accordingly. How is this scenario handled
in MVVM? How does a fragment talk to it's parent activity?
For interaction, you can create ViewModel that is shared between Fragment and Activity. In that case you have an abstraction, where you push some data in ViewModel LiveData where it gets an event whoever listens to same ViewModel.
For example this method is recommended for Fragment to Fragment communications, but I think it also fits Fragment to Activity.
On the regular way I would have inside the fragment's onCreateView,
inflate the layout, use findViewById to get the views and use, for
instance textView.setText() or button.setOnClickListener(). How can
this be done in MVVM? Use DataBinding?
You can use either DataBinding or Kotlin Android Extension, both should be fine with MVVM.
Through DataBinding should be better, since it will reduce boilerplate.
But personally I find Kotlin Android Extensions also very clean.

Android ViewModel recreated on screen rotation

I found a case when architecture components ViewModel isn't retained - in short it goes as follows:
Activity is started and ViewModel instance is created
Activity is put to background
Device screen is rotated
Activity is put back to foreground
ViewModel's onCleared method is called and new object is created
Is it normal behavior of Android that my ViewModel instance is getting destroyed in this case? If so, is there any recommended solution of keeping its state?
One way I can think of is saving it once onCleared is called, however, it would also persist the state whenever activity is actually finishing. Another way could be making use of onRestoreInstanceState but it's fired on every screen rotation (not only if the app is in background).
Any silver bullet to handle such case?
Yes #tomwyr, this was a bug from an android framework. Bug details
The fix is available in 28.0.0-alpha3 and AndroidX 1.0.0-alpha3
But if you don't want to update to above versions now itself, Then you can solve like this (I know this is a bad solution but I didn't see any other good way)
In your activity override onDestroy method and save all the required fields to local variables before calling super.onDestroy. Now call super.onDestroy then Initialize your ViewModel again and assign the required fields back to your new instance of ViewModel
about isFinishing
Below code is in Kotlin:
override fun onDestroy() {
val oldViewModel = obtainViewModel()
if (!isFinishing) { //isFinishing will be false in case of orientation change
val requiredFieldValue = oldViewModel.getRequiredFieldValue()
super.onDestroy
val newViewModel = obtainViewModel()
if (newViewModel != oldViewModel) { //View Model has been destroyed
newViewModel.setRequiredFieldValue(requiredFieldValue)
}
} else {
super.onDestroy
}
}
private fun obtainViewModel(): SampleViewModel {
return ViewModelProviders.of(this).get(SampleViewModel::class.java)
}
AFAIK, ViewModel's only purpose is to survive and keep the data (i.e. "save the state") while its owner goes through different lifecycle events. So you don't have to "save the state" yourself.
We can tell from this that it's "not normal behavior". onCleared() is only called after the activity is finished (and is not getting recreated again).
Are you creating the ViewModel using the ViewModelProvider, or are you creating the instance using the constructor?
In your activity, you should have something like:
// in onCreate() - for example - of your activity
model = ViewModelProviders.of(this).get(MyViewModel.class);
// then use it anywhere in the activity like so
model.someAsyncMethod().observe(this, arg -> {
// do sth...
});
By doing this, you should get the expected effect.
For others that may not be helped by previous answers like me, the problem could be that you haven't set up your ViewModelProvider properly with a factory.
After digging around I solved my similiar problem by adding the following method to my Activities:
protected final <T extends ViewModel> T obtainViewModel(#NonNull AppCompatActivity activity, #NonNull Class<T> modelClass) {
ViewModelProvider.AndroidViewModelFactory factory = ViewModelProvider.AndroidViewModelFactory.getInstance(activity.getApplication());
return new ViewModelProvider(activity, factory).get(modelClass);
}
And then I did this in my Fragments:
protected final <T extends ViewModel> T obtainFragmentViewModel(#NonNull FragmentActivity fragment, #NonNull Class<T> modelClass) {
ViewModelProvider.AndroidViewModelFactory factory = ViewModelProvider.AndroidViewModelFactory.getInstance(fragment.getApplication());
return new ViewModelProvider(fragment, factory).get(modelClass);
}
I already had some abstract super classes for menu purposes so I hid the methods away there so I don't have to repeat it in every activity. That's why they are protected. I believe they could be private if you put them in every activity or fragment that you need them in.
To be as clear as possible I would then call the methods to assign my view model in onCreate() in my activity and it would look something like this
private MyViewModel myViewModel;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
myViewModel = obtainViewModel(this, MyViewModel.class);
}
or in fragment
private MyViewModel myViewModel;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getActivity() != null) {
myViewModel = obtainFragmentViewModel(getActivity(), MyViewModel.class);
}
}
Change support library/compileSDK/targetSDK to 28.
I had similar issue with multi-window. When switching to split screen, my viewModel is recreated. Support library 28 fixed my problem. (My lifecycle version is 1.1.1)

Dagger 2: Is it recommended to use a static provider for Activities and fragments?

I recently watched a talk by Gregory Kick on Dagger 2. There he explained about static provider methods, which could help in performance. So I was modifying all the provider methods in my project to static. But I have a doubt here. In my project, there are some providers which return an activity instance (an external dependency). So I have written module
with a constructor, which takes the Activity from outside.
#Module
public class ActivityModule {
private static BaseActivity mActivity;
public ActivityModule(BaseActivity activity) {
mActivity = activity;
}
#ActivityScope
#Provides
Activity mActivity() {
return mActivity;
}
#ActivityScope
#Provides
BaseActivity baseActivity() {
return mActivity;
}
}
So , If I have to make the providers static it would look something like as follows
#Module
public class ActivityModule {
private static BaseActivity mActivity;
public ActivityModule(BaseActivity activity) {
mActivity = activity;
}
#ActivityScoped
#Provides
static Activity mActivity() {
return mActivity;
}
#ActivityScoped
#Provides
static BaseActivity baseActivity() {
return mActivity;
}
}
So, here I'm keeping the activity instance in a static member. Will it cause any memory leaks? Or does dagger manage it automatically by removing the static reference whenever the activity gets destroyed?
Keeping static providers are recommended in this case?
Is it recommended to use a static provider for Activities and fragments?
No. All you do is create a source for errors and memory leaks. As a rule of thumb, never put any Android Framework types into static variables. They reference a context and you will leak memory.
As mentioned, static methods might provide a performance boost and of course you can use that. The problem with the code you show is the static method that returns a static variable—which also kind of defeats the purpose of calling the module constructor.
If you have a module that only consists of static methods you might gain some additional performance and remove the need of the module instance allocation, as they mention in the linked talk.
Static methods by themselves are okay, but they should not interact with other parts of your app, like read from or write static variables. If you have dependencies on other objects, always add them as parameters to your provides-method. That way Dagger can properly use those methods and you might gain some performance.
What about returning a new fragment instance from a static provider?
#Provides #ActivityScoped static MyFragment provideFragment() {
return MyFragment.createInstance();
}
I believe this could actually be okay. It's a static method that returns a new instance of a Fragment and should not contain any side effects.
Make sure, though, that you re-create your #ActivityScoped component whenever the Activity gets recreated and that the Fragment does not retain its state. You really want to avoid that the FragmentManager manages a different Fragment from the one in your component or, even worse, both.

Android - keep webservice results in memory

In my Android app I have to query some user/session dependent data from a rest webservice. Now I need a way to keep the received webservice results in memory, so that serveral activities/fragments can access them.
I don't want to persist the data (for example a list of the users bank accounts) into a database on the device, because the data expires after a while or when the user logs out.
I also don't want to request the data again and again from webservice, when the user navigates to another activity.
Are there any approved patterns to keep a set of data (some pojo's with more or less properties) in memory during the application is running?
Just for info: I'm experimenting with dagger2, mvp, retrofit2, rxandroid
Regards
Martin
If you already experimenting with Dagger 2, then all you need to do is instantiate a component in Application and use this component in your Activities and Fragments in order to inject a scoped "service".
For example:
Create a class named XyzManager (where Xyz = the actual functionality this manager is responsible for)
Annotate its #Provides method (in Dagger's module) with #Singleton scope
Make sure that the component that injects XyzManager instantiated in Application and add getComponent() method to your custom Appliaction class
In your Activities and Fragments inject XyzManager while using the same component - ((MyApplication)getApplication()).getComponent().inject(this)
If you take the above steps, then all your Activities and Fragments will get a reference to exactly the same instance of XyzManager, and the data you cache in this manager will be accessible everywhere.
The structure you would get is very similar to the structure described in this answer.
Please note that this approach is much better than resolving to static things (e.g. Singleton pattern, or what #KhalidTaha suggested in his answer).
You might want to take a look at my post concerning Dagger 2 scopes if you need a detailed information on that aspect of the framework.
here is a solution:
1- create a DefaultUtil class:
public calss DefaultUtil{
private List<User> listOfUsers;
public static DefaultUtil getInstance(){
if(instance == null)
{
instance = new DefaultUtil();
}
return instance;
}
public List<User> getUserList(){ return listOfUsers; }
public void setUserList(List<User> userList) {
this.listOfUsers = userList ;
}
}
2- when you finish the webservice, call this code:
DefaultUtil.getInstance().setUserList(myWebserviceListOfUsersResult);
and then you can access the list of users from any class by this:
DefaultUtil.getInstance().getUserList();
#Vasiliy
I've studied the linked answer, but I don't get it. I don't use my BankingSession singleton in an activity directly, so calling "getComponent().inject(this).... " won't work. I use the singleton in other service classes (not Android services... just business logic).
// this should be a single instance across the whole app
#Singleton
public class BankingSession {
#Inject
public BankingSession() {
}
}
public class SessionServiceImpl implements SessionService {
private final BankingSession bankingSession;
#Inject
public SessionServiceImpl(BankingSession bankingSession) {
this.bankingSession = bankingSession;
}
}
#Module
public class SessionModule {
#Provides
public SessionService provideSessionService(SessionServiceImpl sessionService) {
return sessionService;
}
}
#Singleton
#Component(modules = {AppModule.class, NetworkModule.class, SessionModule.class})
public interface AppComponent {
Application application();
LoginComponent plus(LoginModule module);
AccountComponent plus(AccountModule module);
BankingSession bankingSession();
}
No matter how I try it, the constructor of BankingSession get's called multiple times

Categories

Resources