Applying MVVM between ViewModel and Fragment/Activity interactions and communication - android

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.

Related

MVVM in android,accessing assetManager without breaking the pattern

I have a JSON file in the assets folder and DataManager(repository) class needs it so assetManager(and context) should have access to the assets.
The problem is that based on Best practice, Android context or android specific code should not be passed into the data layer(ViewModel-Repo-Model) because of writing unit tests or etc easily and also view should not interact with the data layer directly.
I ended up providing the list using and injecting it to the repository.
Is this the right thing to do?
-Thanks
P.S: my Module class which provides the list
#Module
public class UtilModule {
#Provides
#JsonScope
JsonUtil provideJsonUtil(AssetManager assetManager){
return new JsonUtil(assetManager);
}
#Provides
#JsonScope
String provideJson(JsonUtil util){
return util.getJson();
}
#Provides
#JsonScope
Type provideType(){
return new TypeToken<List<Data>>() {}.getType();
}
#Provides
#JsonScope
DataManager provideDataManager (Gson gson, Type type,String json) {
return new DataManager (gson.fromJson(json, type));
}
}
It's not a violation of MVVM for a ViewModel and/or Repository to access the Application context directly, which is all you need to access the AssetsManager. Calling Application.getAssets() is OK because the ViewModel doesn't use any particular Activity's context.
For example, you can use the Google-provided AndroidViewModel subclass instead of the superclass ViewModel. AndroidViewModel takes an Application in its constructor (ViewModelProviders will inject it for you). You could pass your Application to your Repository in its constructor.
Alternately, you could use Dagger dependency injection to inject an Application directly into your Repository. (Injecting the Application context is a bit tricky. See Dagger 2 injecting Android Context and this issue filed on the Danger github repo.) If you want to make it really slick, you could configure a provider for AssetManager and inject it directly into your Repository.
Finally, if you are using Room, and all you want is to pre-populate your Room database with a pre-configured database stored in assets, you can follow instructions here: How to use Room Persistence Library with pre-populated database?
Since you are using MVVM for the first time, we can try to keep things simple.
[ View Component C] ---- (observes) [ ViewModel Component B ] ---- [ Repository ]
According to the Separation of Concerns rule, the ViewModel should expose LiveData. LiveData uses Observers to observe data changes. The purpose of the ViewModel is to separate the data layer from UI. ViewModel should not know about Android framework classes.
In MVVM Architecture, the ViewModel's role is to fetch data from a Repository. You can consider either storing your json file as a local data source using Room, or keeping the Json API as a remote data source. Either way, the general implementation is as follows:
Component A - Entity (implements your getters & setters)
Method 1: Using Room
#Entity(tableName = "file")
public class FileEntry{
#PrimaryKey(autoGenerate = true)
private int id;
private String content; // member variables
public FileEntry(String content){ // constructor
this.id = id;
this.content = content;
}
public int getId(){ // getter methods
return id;
}
public void setId(int id){ // setter methods
this.id = id;
}
public String getContent(){
return content;
}
public void setContent(String content){
this.content = content;
}
}
Method 2: Using Remote Data Source
public class FileEntry implements Serializable{
public String getContent(){
return content;
}
private String content;
}
Component B - ViewModel (Presentation Layer)
Method 1: Using Room
As you asked about how android context can be passed, you can do so by extending AndroidViewModel like below to include an application reference. This is if your database requires an application context, but the general rule is that Activity & Fragments should not be stored in the ViewModel.
Supposing you have "files" as a member variable defined for your list of objects, say in this case, "FileEntry" objects:
public class FileViewModel extends AndroidViewModel{
// Wrap your list of FileEntry objects in LiveData to observe data changes
private LiveData<List<FileEntry>> files;
public FileViewModel(Application application){
super(application);
FilesDatabase db = FilesDatabase.getInstance(this.getApplication());
Method 2: Using Remote Data Source
public class FileViewModel extends ViewModel{
public FileViewModel(){}
public LiveData<List<FileEntry>> getFileEntries(String content){
Repository repository = new Repository();
return repository.getFileEntries(content);
}
}
In this case, getFileEntries method contains MutableLiveData:
final MutableLiveData<List<FileEntry>> mutableLiveData = new MutableLiveData<>();
If you are implementing using Retrofit client, you can do something similar to below code using asynchronous callbacks. The code was taken from Retrofit 2 Guide at Future Studio with some modifications for this discussion example.
// asynchronous
call.enqueue(new Callback<ApiData>() {
#Override
public void onResponse(Call<ApiData> call, Response<ApiData> response) {
if (response.isSuccessful()) {
mutableLiveData.setValue(response.body().getContent());
} else {
int statusCode = response.code();
// handle request errors yourself
ResponseBody errorBody = response.errorBody();
}
}
#Override
public void onFailure(Call<ApiData> call, Throwable t) {
// handle execution failures like no internet connectivity
}
return mutableLiveData;
Component C - View (UI Controller)
Whether you are using Method 1 or 2, you can do:
FileViewModel fileViewModel = ViewModelProviders.of(this).get(FileViewModel.class);
fileViewModel.getFileEntries(content).observe(this, fileObserver);
Hope this is helpful.
Impacts on Performance
In my opinion, deciding whether to use which method may hinge on how many data calls you are implementing. If multiple, Retrofit may be a better idea to simplify the API calls. If you implement it using Retrofit client, you may have something similar to below code taken as provided from this reference article on Android Guide to app architecture:
public LiveData<User> getUser(int userId) {
LiveData<User> cached = userCache.get(userId);
if (cached != null) {
return cached;
}
final MutableLiveData<User> data = new MutableLiveData<>();
userCache.put(userId, data);
webservice.getUser(userId).enqueue(new Callback<User>() {
#Override
public void onResponse(Call<User> call, Response<User> response) {
data.setValue(response.body());
}
});
return data;
}
The above implementation may have threading performance benefits, as Retrofit allows you to make asynchronous network calls using enqueue & return the onResponse method on a background thread. By using method 2, you can leverage Retrofit's callback pattern for network calls on concurrent background threads, without interfering with the main UI thread.
Another benefit of the implementation above is that if you are making multiple api data calls, you can cleanly get the response through an interface webservice above, for your LiveData. This allows us to mediate responses between different data sources. Then, calling data.setValue sets the MutableLiveData value & then dispatches it to active observers on the main thread, as per Android documentation.
If you are already familiar with SQL & only implementing 1 database, opting for the Room Persistence Library may be a good option. It also uses the ViewModel, which brings performance benefits since chances of memory leaks are reduced, as ViewModel maintains fewer strong references between your UI & data classes.
One point of concern may be, is your db repository (example, FilesDatabase implemented as a singleton, to provide a single global point of access, using a public static method to create the class instance so that only 1 same instance of the db is opened at any one time? If yes, the singleton might be scoped to the application scope, & if the user is still running the app, the ViewModel might be leaked. Thus make sure your ViewModel is using LiveData to reference to Views. Also, it might be helpful to use lazy initialization so that a new instance of the FilesDatabase singleton class is created using getInstance method if there are no previous instances created yet:
private static FilesDatabase dbInstance;
// Synchronized may be an expensive operation but ensures only 1 thread runs at a time
public static synchronized FilesDatabase getInstance(Context context) {
if (dbInstance == null) {
// Creates the Room persistent database
dbInstance = Room.databaseBuilder(context.getApplicationContext(), FilesDatabase.class, FilesDatabase.DATABASE_NAME)
Another thing is, no matter your choice of Activity or Fragment for your UI, you will be using ViewModelProviders.of to retain your ViewModel while a scope of your Activity or Fragment is alive. If you are implementing different Activities/Fragments, you will have different instances of ViewModel in your application.
If for example, you are implementing your database using Room & you want to allow your user to update your database while using your application, your application may now need the same instance of the ViewModel across your main activity and the updating activity. Though an anti-pattern, ViewModel provides a simple factory with an empty constructor. You can implement it in Room using public class UpdateFileViewModelFactory extends ViewModelProvider.NewInstanceFactory{:
#Override
public <T extends ViewModel> T create(#NotNull Class<T> modelClass) {
return (T) new UpdateFileViewModel(sDb, sFileId);
Above, T is a type parameter of create. In the factory method above, the class T extends ViewModel. The member variable sDb is for FilesDatabase, and sFileId is for the int id that represents each FileEntry.
This article on Persist Data section by Android may be more useful than my comments if you would like to find out more, on performance costs.

Why do we need LiveData and ViewModel

I have used LiveData and ViewModel example
but i dont understand use of this feature because i can change value directly without use this feature even this is growing number of line in code by using observing code and same as in ViewModel by creating MutableLiveData.
below ViewModel Code
public class FirstViewModel extends ViewModel {
// Create a LiveData with a String
public MutableLiveData<String> mCurrentName;
public MutableLiveData<String> getCurrentName() {
if (mCurrentName == null) {
mCurrentName = new MutableLiveData<String>();
}
return mCurrentName;
}
}
Using in Activity
public class MainActivity extends AppCompatActivity {
private FirstViewModel mModel;
ActivityMainBinding mBinding;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding=DataBindingUtil.setContentView(this,R.layout.activity_main);
// Get the ViewModel.
mModel= ViewModelProviders.of(this).get(FirstViewModel.class);
// Create the observer which updates the UI.
final Observer<String> nameObserver = new Observer<String>() {
#Override
public void onChanged(#Nullable final String newName) {
// Update the UI, in this case, a TextView.
mBinding.mNameTextView.setText(newName);
}
};
// Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
mModel.getCurrentName().observe(this, nameObserver);
mBinding.btnSubmit.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
String anotherName = mBinding.etField.getText().toString();
mModel.getCurrentName().setValue(anotherName);
}
});
}
}
The ViewModel and LiveData android architecture components together help to create lifecycle aware applications.
ViewModel:
ViewModel classes are often used to significantly segregate the view logic (present in Activity classes) from the business logic which is contained in the ViewModel classes. This segregation is a good architecture design and becomes very important while maintaining large projects.
LiveData:
LiveData helps in implementing the Observer Observable pattern in a lifecycle aware manner.
In your case, it may seem trivial since you are only setting value for a TextView. However consider common scenarios like hitting an api to retrieve data, etc. In such cases, the ViewModel is responsible for providing the data to be displayed in the Activity, which when done with the help of LiveData can help avoid crashes by ensuring lifecycle awareness easily.
You can read about live data from here. It is like Observer that looks for changing of data and notify observers that observable object has changed
In simple words its make your life eazy as a programmer when we go into the details like activity/fragment lifecycle handling, displaying updated data and more importantly separating the presentation layer from business logic and to create a more well structured application. please find more details from here

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

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.

Understanding scopes in Dagger 2

I have an scope-related error in Dagger 2 and I'm trying to understand how I can solve it.
I have a CompaniesActivity that shows companies. When the user selects an item, selected company's employees are shown in EmployeesActivity. When the user selects an employee, her detail is shown in EmployeeDetailActivity.
class Company {
List<Employee> employees;
}
Class CompaniesViewModel contains the companies and the selected one (or null):
class CompaniesViewModel {
List<Company> companies;
Company selected;
}
CompaniesActivity has a reference to CompaniesViewModel:
class CompaniesActivity extends Activity {
#Inject
CompaniesViewModel viewModel;
#Override
protected void onCreate(Bundle b) {
//more stuff
getComponent().inject(this);
showCompanies(viewModel.companies);
}
//more stuff
private onCompanySelected(Company company) {
viewModel.selected = company;
startActivity(new Intent(this, EmployeesActivity.class));
}
}
Class EmployeesViewModel contains the employees and the selected one (or null):
class EmployeesViewModel {
List<Employee> employees;
Employee selected;
}
EmployeesActivity has a reference to EmployeesViewModel:
class EmployeesActivity extends Activity {
#Inject
EmployeesViewModel viewModel;
#Override
protected void onCreate(Bundle b) {
//more stuff
getComponent().inject(this);
showEmployees(viewModel.employees);
}
//more stuff
private onEmployeeSelected(Employee emp) {
viewModel.selected = emp;
startActivity(new Intent(this, EmployeeDetailActivity.class));
}
}
Finally, in EmployeeDetailActivity, I get selected Employee from view model and show her detail:
class EmployeeDetailActivity extends Activity {
#Inject
EmployeesViewModel viewModel;
#Override
protected void onCreate(Bundle b) {
//more stuff
getComponent().inject(this);
showEmployeeDetail(viewModel.selected); // NullPointerException
}
}
I get NullPointerException because EmployeesViewModel instance in EmployeesActivity is not the same as the EmployeeDetailActivity and, in the second one, viewModel.selected is null.
This is my dagger module:
#Module
class MainModule {
#Provides
#Singleton
public CompaniesViewModel providesCompaniesViewModel() {
CompaniesViewModel cvm = new CompaniesViewModel();
cvm.companies = getCompanies();
return cvm;
}
#Provides
public EmployeesViewModel providesEmployeesViewModel(CompaniesViewModel cvm) {
EmployeesViewModel evm = new EmployeesViewModel();
evm.employees = cvm.selected.employees;
return evm;
}
}
Note that CompaniesViewModel is singleton (#Singleton) but EmployeesViewModel is not, because it has to be recreated each time user selects a company (employees list will contain other items).
I could set the company's employees to EmployeesViewModel each time user selects a company, instead of create a new instance. But I would like CompaniesViewModel to be immutable.
How can I solve this? Any advise will be appreciated.
Unfortunately, I think that you abuse DI framework in this case, and the issues that you encounter are "code smells" - these issues hint that you're doing something wrong.
DI frameworks should be used in order to inject critical dependencies (collaborator objects) into top level components, and the logic that performs these injections should be totally independent of the business logic of your application.
From the first sight everything looks fine - you use Dagger in order to inject CompaniesViewModel and EmployeesViewModel into Activity. This could have been fine (though I wouldn't do it this way) if these were real "Objects". However, in your case, these are "Data Structures" (therefore you want them to be immutable).
This distinction between Objects and Data Structures is not trivial, but very important. This blog post summarizes it pretty well.
Now, if you try to inject Data Structures using DI framework, you ultimately turn the framework into "data provider" of the application, thus delegating part of the business functionality into it. For example: it looks like EmployeesViewModel is independent of CompaniesViewModel, but it is a "lie" - the code in #Provides method ties them together logically, thus "hiding" the dependency. Good "rule of thumb" in this context is that if DI code depends on implementation details of the injected objects (e.g. calls methods, accesses fields, etc.) - it is usually an indication of insufficient separation of concerns.
Two specific recommendations:
Don't mix business logic with DI logic. In your case - don't inject data structures, but inject objects that either provide access to the data (bad), or expose the required functionality while abstracting the data (better).
I think that your attempt of sharing a View-Model between multiple screens is not a very robust design. It would be better to have a separate instance of View-Model for each screen. If you need to "share" state between screens, then, depending on the specific requirements, you could do this with 1) Intent extras 2) Global object 3) Shared prefs 4) SQLite
According to this article about Custom Scopes:
http://frogermcs.github.io/dependency-injection-with-dagger-2-custom-scopes/
In short - scopes give us “local singletons” which live as long as scope itself.
Just to be clear - there are no #ActivityScope or #ApplicationScope annotations provided by default in Dagger 2. It’s just most common usage of custom scopes. Only #Singleton scope is available by default (provided by Java itself), and the point is using a scope is not enough(!) and you have to take care of component that contains that scope. This mean keeping a reference to it inside Application class and reuse it when Activity changes.
public class GithubClientApplication extends Application {
private AppComponent appComponent;
private UserComponent userComponent;
//...
public UserComponent createUserComponent(User user) {
userComponent = appComponent.plus(new UserModule(user));
return userComponent;
}
public void releaseUserComponent() {
userComponent = null;
}
//...
}
You can take a look at this sample project:
http://github.com/mmirhoseini/marvel
and this article:
https://hackernoon.com/yet-another-mvp-article-part-1-lets-get-to-know-the-project-d3fd553b3e21
to get more familiar with MVP and learn how dagger scope works.
There are a couple of issues here and that are only obliquely related to Dagger 2 scopes.
Firstly, the fact that you have used the term "ViewModel" suggests you are trying to use MVVM architecture. One of the salient features of MVVM is separation of layers. However, your code has not achieved any separation between model and view-model.
Let's take a look at this definition of model from Eric Evans:
A domain model is a system of abstractions that describes selected aspects of a sphere of knowledge, influence, or activity (a domain).2
Here your sphere of knowledge is a company and its employees. Looking at your EmployeesViewModel, it contains at least one field that is probably better isolated in the model layer.
class EmployeesViewModel {
List<Employee> employees; //model layer
Employee selected;
}
Perhaps it is merely an unfortunate choice of name, but I think your intention is to create proper view-models so any answer to this question should address that. While selection is associated with the view, the class doesn't really qualify as an abstraction of the view. A real view-model would probably somehow match the way the Employee is displayed on the screen. Let's say you have "name" and "date of birth" TextViews. Then a view-model would expose methods that provide the text, visibility, color etc. for those TextViews.
Secondly, what you are proposing is using (singleton) Dagger 2 scopes to communicate between Activities. You want the company selected in the CompaniesActivity to be communicated to the EmployeesActivity and the employee selected in EmployeesActivity to be communicated to the EmployeeDetailActivity.
You are asking how to achieve this by making them all use the same shared global object.
While this may be technically possible using Dagger 2, the correct approach in Android to communicate between Activities is to use intents, rather than shared objects. The answers to this question are a really good explanation of this point.
Here is a proposed solution:
It's not clear what you are doing to actually get the List<Company>. Maybe you are getting from a db, maybe you are getting from a cached web request. Whatever it is, encapsulate this in an object CompaniesRepository. Likewise for EmployeesRepository.
So you will have something like:
public abstract class EmployeesRepository {
List<Employee> getAll();
Employee get(int id);
int getId(Employee employee);
}
Do something similar for a CompaniesRepository class. These two retrieval classes can be singletons and be initialised in your module.
#Module
class MainModule {
#Provides
#Singleton
public CompaniesRepository(Dependency1 dependency1) {
//TODO: code you need to generate the companies retrieval object
}
#Provides
#Singleton
public EmployeesRepository(Dependency2 dependency2) {
//TODO: code you need to generate the employees retrieval object
}
}
Your EmployeesActivity now looks something like this:
class EmployeesActivity extends Activity {
#Inject CompaniesRepository companiesRepository;
#Inject EmployeesRepository employeesRepository;
#Override
protected void onCreate(Bundle b) {
//more stuff
getComponent().inject(this);
//retrieve the id of the company selected in the previous activity
//and use that to get the company model
int selectedCompanyId = b.getIntExtra(BUNDLE_COMPANY_ID, -1);
//TODO: handle case where no company id has been passed into the activity
Company selectedCompany = companiesRepository.get(selectedCompanyId);
showEmployees(selectedCompany.getEmployees);
}
//more stuff
private onEmployeeSelected(Employee emp) {
int selectedEmployeeId = employeesRepository.getId(emp);
Intent employeeDetail = new Intent();
employeeDetail.putExtra(BUNDLE_EMPLOYEE_ID, selectedEmployeeId);
startActivity(employeeDetail));
}
}
Extend this example to your other two activities and you will be approaching the standard architecture for an Android app and you will be using Dagger 2 without mixing your model layer, view layer etc.

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