I have multiple instances of same viewmodel in different fragments(scoped to fragments). Whenever I update LiveData in Room, it triggers observable in fragment but it doesn't trigger one instance that should but all of instances(even though the values in others are same). I have thought of a solution as Transformations.distinctUntilChanged but it is not working. My code:
#Dao
public interface PlayerDao {
#Query("SELECT * from player_table WHERE id_playera=:id")
LiveData<PlayerEntity> getPlayer(final int id);
}
public class PlayerRepository {
public LiveData<PlayerEntity> getPlayer(final int id) {
return playerDao.getPlayer(id);
}
}
public class PlayerViewModel extends AndroidViewModel {
public LiveData<PlayerEntity> getPlayer(final int id) {
return Transformations.distinctUntilChanged(repository.getPlayer(id));
}
}
#Override
public void onViewCreated(#NonNull final View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
playerViewModel = new ViewModelProvider(this).get(PlayerViewModel.class);
playerViewModel.getPlayer(redniBrojPlayera).observe(getViewLifecycleOwner(),
newObserver<PlayerEntity>() {
#Override
public void onChanged(PlayerEntity playerEntity) {
//triggers UI
}
});
}
I tried fetching Livedata-Integer from Room and then applying Transformations.distinctUntilChanged() in viewmodel and it worked as intended only triggering one that changed. So I am wondering is it even possible to do this and why are observables triggering even through transformations.
The problem was that I didn't Override equals method in my Entity.class. Therefore it would return completely new object and Transformation.distinctUntilChanged would trigger.
Related
I have a view model class, SampleViewModel, that has an observer, DataSourceObserver, whose event's (e.g. onDataUpdated) are triggered from a private method of the view model's data source class.
I am trying to add a unit test for how the view model's observer handles the onDataUpdated.
#HiltViewModel
public class SampleViewModel extends ViewModel {
private final #NonNull ViewModelDataSource viewModelDataSource;
private final #NonNull ViewModelDataSource.DataSourceObserver observer = new ViewModelDataSource.DataSourceObserver() {
#Overrode
public void onDataUpdated(){
// some sort of handling
}
};
#Inject
public SampleViewModel(final #NonNull ViewModelDataSource dataSource){
viewModelDataSource = dataSource;
viewModelDataSource.setObserver(observer);
}
}
public class ViewModelDataSource{
public interface DataSourceObserver {
void onDataUpdated();
}
private final ContactObservers.Observer contactObserver = new ContactObservers.Observer(){
#Override
public void onContactUpdated(){
if (myObserver != null){
myObserver.onDataUpdated();
}
}
};
private #Nullable DataSourceObserver myObserver;
#Inject
public ViewModelDataSource(){
// other initialization here
}
public void setObserver(DataSourceObserver observer){
myObserver = observer;
}
}
I have Mockito for mocking/stubbing objects, but I have no idea how to approach such a problem so that I can at least trigger something like observer.onDataUpdated and verify the method calls within the handling of onDataUpdated
Found a solution to use ArgumentCaptor to capture the initialized observer in the view model when viewModelDataSource.setObserver(observer); is called.
In my unit test:
ArgumentCaptor<ViewModelDataSource.DataSourceObserver> capturedObserver = ArgumentCaptor.forClass(ViewModelDataSource.DataSourceObserver.class);
SampleViewModel viewModel = new SimpleViewModel( ...);
assertNotNull(capturedObserver);
I tried to use DiffUtil approach to update my list which always consist of 30 items, now each item data updates every minute but no way of telling if all item's data will have updates so to avoid abusing notifyDataSetChanged() I create a class extending DiffUtil.
public class DifUtil extends DiffUtil.Callback {
private final List<Asset> oldList, newList;
public DifUtil(List<Asset> newList, List<Asset> oldList) {
this.oldList = oldList;
this.newList = newList;
}
#Override
public int getOldListSize() {
return oldList.size();
}
#Override
public int getNewListSize() {
return newList.size();
}
#Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return oldList.get(oldItemPosition).getId().equals(newList.get(newItemPosition).getId());
}
#Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return oldList.get(oldItemPosition).equals(newList.get(newItemPosition));
}
#Nullable
#Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
//you can return particular field for changed item.
return super.getChangePayload(oldItemPosition, newItemPosition);
}
}
Add new public function to notify the adapter with this
public void updateList(List<Asset> newList) {
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DifUtil(newList, this.assetList));
this.assetList.clear();
this.assetList.addAll(newList);
diffResult.dispatchUpdatesTo(this);
}
Overriding another onBindViewHolder (not sure if needed when not using payload)
onBindViewHolder(#NonNull AssetsAdapter.ViewHolder holder, int position, #NonNull List<Object> payloads)
Then updating the list by just calling
adapter.updateList(newAssetList);
The updating of list works but I can only see those new values by scrolling the list, I need to view the updates even without recycling the view (when scrolling) just like notifyItemChanged().
To my understanding calling dispatchUpdatesTo should handle and update the views and its data or am I missing something here please enlighten me.
I managed to make it work by following this blog and its way of implementing DiffUtil.
https://www.journaldev.com/20873/android-recyclerview-diffutil
However I did not perform the cloning part since it is irrelevant on my case.
BONUS
There is a known bug where updates makes the list to scroll at the bottom. I get rid of this by saving scroll state with LayoutManager like in this SO answer.
https://stackoverflow.com/a/44053550/11338467
So my adapter DiffUtil update part will be this
public void updateList(List<Asset> newList) {
Parcelable recyclerViewState = layoutManager.onSaveInstanceState();
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DifUtil(newList, this.assetList));
diffResult.dispatchUpdatesTo(this);
this.assetList.clear();
this.assetList.addAll(newList);
layoutManager.onRestoreInstanceState(recyclerViewState);
}
I want to get the id auto-generated while performing insert operation on Room database. I am implementing MVVM (Model-View-ViewModel) architecture which makes use of DAO to fire queries to Room database. I have added a repository layer between viewmodel and DAO to create an AsyncTask to perform database operations. How do I get the output of insert operation (which is the inserted row's auto-generated id) to the fragment that uses the viewmodel. The layers are as follows: Fragment -> ViewModel -> Repository -> DAO
ListFragment.java
public class ListFragment extends Fragment {
private ReminderViewModel viewModel;
private int id;
...
viewModel = ViewModelProviders.of(this).get(ReminderViewModel.class);
...
id = viewModel.insert(new TodoReminder(0, description, date, time));
...
}
ReminderViewModel.java
public class ReminderViewModel extends AndroidViewModel {
private ReminderRepository repository;
public ReminderViewModel(#NonNull Application application) {
super(application);
repository = new ReminderRepository(application);
}
public int insert(TodoReminder reminder) {
repository.insert(reminder);
}
}
ReminderRepository.java
public class ReminderRepository {
private ReminderDAO reminderDAO;
public ReminderRepository(Application application) {
AppDatabase db = AppDatabase.getDatabase(application);
reminderDAO = db.getReminderDAO();
}
public int insert(TodoReminder reminder) {
new insertAsyncTask(reminderDAO).execute(reminder);
}
private static class InsertAsyncTask extends AsyncTask<TodoReminder, Void, Integer> {
private ReminderDAO asyncTaskDAO;
insertAsyncTask(ReminderDAO dao) {
asyncTaskDAO = dao;
}
#Override
protected Integer doInBackground(final TodoReminder... reminders) {
return asyncTaskDAO.insert(reminders[0]);
}
}
}
ReminderDAO.java
#Dao
public interface ReminderDAO {
#Insert
public int insert(TodoReminder... reminders);
}
ToDoReminder.java
public class TodoReminder implements Serializable {
#PrimaryKey(autoGenerate = true)
#NonNull
private int id;
...
}
How should I get the int returned from the insert method of ReminderDAO and return it from the insert method in ReminderRepository?
You can create your database table in such a way that the id is incremented automatically. In MySQL that is done via the auto_increment keyword. In SQL Server it is done via the identity(1, 1) syntax. In Access it is the autoincrement keyword. In Oracle and PostgreSQL it is done using sequences. If you manage to do this, then you will not need to manually work on incrementing these values. If, for some reason this is out of the question, then you can create a before insert trigger which will load the maximum id and add 1 to it, storing the result in the id. Or, if even that is out of the question, then you can load the last ID, but that has a different syntax in different databases. Or, you can run a query like this:
select max(id) + 1 from yourtable;
but beware possible performance issues and concurrency problems.
I have the exact problem. This is what I did.
I created an interface
public interface Task
{
void processInsert(long id)
}
supply the interface to the insert fcn in the repository
public class Repository {
private Dao myDao;
public void insertMyObject(MyOBject object,Task myInterface ) {
new insertAysncTask(myDao,myInterface).execute(object);
}
private static class insertAsyncTask extends AysncTask<MyObject,Void,Long>{
private Dao mDao;
private Task mTask;
public insertAysncTask(Dao dao, Task task) {
this.mDao = dao;
this.mTask=task;
}
#Override
protected Long doInBackground(MyObject... myObjects) {
return mDao.insertMyObject(myObjects[0]);
}
#Override
protected void onPostExecute(Long aLong) {
super.onPostExecute(aLong);
mTask.processInsert(aLong);
}
}
}
in the DAO class fcn should have return type of Long
public interface MyDao {
#Insert
Long insertMyObject(MyObject object);
have the ViewModel implement the interface
public class MyObjectViewModel extends AndroidViewModel implements Task{
private Repository mRepository;
public MyObjectViewModel(#NonNull Application application) {
super(application);
mRepository = new Repository(application);
}
#Override
public void processInsert(Long id) {
// code for processing the id returned from the db insert
}
public void insertMyObject(MyObject object){
mRepository.insertMyObject(object,this);}
}
in the activity/fragment call the ViewModel insert
mViewModel.insertMyObject(object);
Update
If you want to return the id to the activity or fragment then have the fragment/activity implement the interface instead of the viewmodel
ListFragment extends Fragment implements Task{
.....
#Override
public void processInsert(Long id){
//process id here
}
//call the insert fcn passing in the reference to the fragment
mViewModel.insertMyObject(object,this)
}
Modify the insert fcn in the View Model to accept a reference to the interface
public void insertMyObject(MyObject object, Task myInterface){
mRepository.insertMyObject(object,myInterface);
}
with this approach, the asynctask in the repository insert fcn will hold a reference to the activity/fragment and if the activity/fragment is destroyed before the asynctask finishes this will be a problem. I think it's better to do the processing in the viewmodel if possible. Fragments/activities should only deal with UI concerns not data processing.
Alternative Method
An alternative would be to use a LiveData with an observer.
public class Repository{
private Dao myDao;
private MutableLiveData<Long> dbInsertId = new MutableLiveData<>();
public void insertMyObject(MyObject object){
insertAysnc(object)
}
private void insertAysnc(final MyObject object){
new Thread(new Runnable() {
#Override
public void run() {
Long id=myDao.insertMyObject(object); //call Dao insert fcn
dbInsertId.postValue(id); //set the value of the livedata to the valued returned from the DB insert fcn
}
}).start();
}
public LiveData<Long> getDbInsertedId(){
return dbInsertId;
}
}
In the ViewModel define this fcn
public LiveData<Long> getDbInsertedId(){
return mRepository.getDbInsertedId();// call the repository getId fcn
}
in the onCreate of Activity/Fragment setup an observer on the LiveData
mViewModel= ViewModelProviders.of(this).get(MyViewModel.class);
mViewModel.getDbInsertedId().observe(this, new Observer<Long>() {
#Override
public void onChanged(Long aLong) {
// do something with the long value returned from the database
}
});
I am trying to load the loggedInUser from the Local Room Database, when the App starts. I would like to skip prompting user to log-in if the saved Authentication Token of the previously saved user is still valid!
So, from the DAO, I want to return a LiveData object containing the previously logged-in user, then observe it for subsequent changes. The challenge I have is that the method to get the currently logged-in user always returns null if I wrap the result inside a LiveData, but it returns the expected user if returned as a POJO.
How can I force LiveData to run synchronously just to initialize the value and then thereafter listen to subsequent changes? I really want to combine the two behaviors as the authentication may be invalidated by a background syncing task or when the user logs out(these actions will either replace or update the saved token and I would like to be reactive to such updates with the help of LiveData).
Here is what I have tried so far:
AuthorizationDAO.java
public interface AuthorizationDAO {
#Query("SELECT * FROM Authorization LIMIT 1") //Assume only one Authentication token will exist at any given time
LiveData<Authorization> getLoggedInUser(); //I want to keep this behaviour
#Insert(onConflict = REPLACE)
long insertAuth(Authorization authorization);
#Update
void logoutCurrentUser(Authorization authorization);
}
AuthorizationRepository.java
public class AuthorizationRepository {
private AuthorizationDAO mAuthorizationDAO;
private MutableLiveData<Authorization> mAuthorization = new MutableLiveData<>();
public AuthorizationRepository(Application application){
AppDatabase db = AppDatabase.getDatabase(application);
this.mAuthorizationDAO = db.mAuthorizationDAO();
}
public LiveData<Authorization> getLoggedInUser(){
mAuthorization.postValue(mAuthorizationDAO.getLoggedInUser().getValue()); //this is always null at startup
return this.mAuthorization;
}
AuthorizationViewModel.java
public class AuthorizationViewModel extends AndroidViewModel {
private AuthorizationRepository mAuthorizationRepository;
private LiveData<Resource<Authorization>> mAuthorization;
private LiveData<Authorization> loggedInUserAuth;
public AuthorizationViewModel(#NonNull Application application) {
super(application);
this.mAuthorizationRepository = new AuthorizationRepository(application);
}
public void init(){
this.loggedInUserAuth = this.mAuthorizationRepository.getLoggedInUser();
}
public LiveData<Authorization> getLoggedInUserAuth() {
return this.loggedInUserAuth;
}
}
AppActivity.java
public class AppActivity extends AppCompatActivity {
public AuthorizationViewModel mAuthorizationViewModel;
public #Nullable Authorization mAuthorization;
private NavController mNavController;
private NavHostFragment mNavHostFragment;
private BottomNavigationView mBottomNavigationView;
private boolean mIsLoggedIn;
private ActivityAppBinding mBinding;
private boolean mIsTokenExpired;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_app);
mNavHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.app_nav_host_fragment);
mNavController = mNavHostFragment.getNavController();
mBottomNavigationView = findViewById(R.id.nav_bottom_nav_view);
NavigationUI.setupWithNavController(mBottomNavigationView, mNavController);
if (Build.VERSION.SDK_INT>9){
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
}
mAuthorizationViewModel = ViewModelProviders.of(this).get(AuthorizationViewModel.class);
mAuthorizationViewModel.init(); //Here I want to load user synchronously before the rest happens and then on next line observe the same object
mAuthorizationViewModel.getLoggedInUserAuth().observe(this, new Observer<Authorization>() {
#Override
public void onChanged(#Nullable Authorization authorization) {
mBinding.setViewModel(authorization);
mIsLoggedIn = authorization == null? false: authorization.isLoggedIn();
mIsTokenExpired = authorization == null ? true : authorization.isTokenExpired();
if(!mIsLoggedIn || mIsTokenExpired){
if (authorization != null){
Log.i("CurrentAuth", "mIsLoggedIn?: "+authorization.isLoggedIn());
Log.i("CurrentAuth", "isTokenExpired?: "+authorization.isTokenExpired());
Log.i("CurrentAuth", "tokenCurrentTime?: "+ Calendar.getInstance().getTime());
Log.i("CurrentAuth", "tokenIssuedAt?: "+ authorization.getIat());
Log.i("CurrentAuth", "tokenExpiresAt?: "+ authorization.getExp());
}
mNavController.navigate(R.id.start_login);
}
}
});
As you can see, I am calling mAuthorizationViewModel.init() so I can load or initialize the loggedInUserAuth from the local database, and then observe the same LiveData instance with mAuthorizationViewModel.getLoggedInUserAuth().observe() on the next line! But the value returned for loggedInUserAuth is always null!
Kindly help, thanks!
I finally solved this problem with great help from #Krishna, and here are the main points:
The DAO method should return LiveData
In the Repository class, create a LiveData private member variable and not MutableLiveData(this is because we will be mutating database record via updates/inserts). The member variable will hold a reference to a LiveData object returned by the DAO Method
In the Repository's constructor, initialize the LiveData object to the result returned by the DAO method. This way, every time the activity starts, the currently saved record will be loaded
In the Repository class, create a getter which will expose the LiveData object to the ViewModel
In the ViewModel class, create a method which will expose the LiveData object to the View Controller (activity or fragment)
In the Activity or Fragment, simply listen or subscribe to changes on the LiveData exposed by the Accessor Method provided by the ViewModel
The DAO can also expose a method to update the LiveData, allowing the Repository via the ViewModel to enable the Activity or Fragment to send updates to the LiveData, at the same time keeping all listeners reactive!
Here is the working code for this scenario:
AuthorizationDAO.java
public interface AuthorizationDAO {
#Query("SELECT * FROM Authorization LIMIT 1") //Assume only one Authentication token will exist at any given time
LiveData<Authorization> getLoggedInUser(); //I want to keep this behaviour
#Insert(onConflict = REPLACE)
long insertAuth(Authorization authorization);
#Update
void logoutCurrentUser(Authorization authorization); //this will be used to toggle login status by Activity or Fragment
}
AuthorizationRepository.java
public class AuthorizationRepository {
private AuthorizationDAO mAuthorizationDAO;
private AuthorizationWebAPI mAuthorizationWebAPI;
private LiveData<Authorization> mAuthorization; //reference to returned LiveData
public AuthorizationRepository(Application application){
AppDatabase db = AppDatabase.getDatabase(application);
this.mAuthorizationDAO = db.mAuthorizationDAO();
this.mAuthorization = mAuthorizationDAO.getLoggedInUser(); //initialize LiveData
}
public LiveData<Authorization> getAuthorizationResult() { //getter exposing LiveData
return mAuthorization;
}
public void logoutCurrentUser(){ //toggle login status
if (this.mAuthorization != null){
AppExecutors.getInstance().getDiskIO().execute(()->{
Authorization mAuthorizationObj = this.mAuthorization.getValue();
mAuthorizationObj.setLoggedIn(false);
mAuthorizationDAO.logoutCurrentUser(mAuthorizationObj); //update LiveData and changes will be broadcast to all listeners
});
}
}
}
AuthorizationViewModel.java
public class AuthorizationViewModel extends AndroidViewModel {
private AuthorizationRepository mAuthorizationRepository;
public AuthorizationViewModel(#NonNull Application application) {
super(application);
this.mAuthorizationRepository = new AuthorizationRepository(application);
}
public LiveData<Authorization> getLoggedInUserAuth() { //exposes LiveData to the Activity or Fragment
return mAuthorizationRepository.getAuthorizationResult();
}
public void logoutCurrentUser(){ //allows activity or fragment to toggle login status
this.mAuthorizationRepository.logoutCurrentUser();
}
}
AppActivity.java
public class AppActivity extends AppCompatActivity {
public AuthorizationViewModel mAuthorizationViewModel;
public #Nullable Authorization mAuthorization;
private NavController mNavController;
private NavHostFragment mNavHostFragment;
private BottomNavigationView mBottomNavigationView;
private boolean mIsLoggedIn;
private ActivityAppBinding mBinding;
private boolean mIsTokenExpired;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_app);
mNavHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.app_nav_host_fragment);
mNavController = mNavHostFragment.getNavController();
mBottomNavigationView = findViewById(R.id.nav_bottom_nav_view);
NavigationUI.setupWithNavController(mBottomNavigationView, mNavController);
mAuthorizationViewModel = ViewModelProviders.of(this).get(AuthorizationViewModel.class);
mAuthorizationViewModel.getLoggedInUserAuth().observe(this, new Observer<Authorization>() { //Observe changes to Authorization LiveData exposed by getLoggedInUserAuth()
#Override
public void onChanged(#Nullable Authorization authorization) {
mBinding.setViewModel(authorization);
mIsLoggedIn = authorization == null? false: authorization.isLoggedIn();
mIsTokenExpired = authorization == null ? true : authorization.isTokenExpired();
if(!mIsLoggedIn || mIsTokenExpired){
if (authorization != null){
Log.i("CurrentAuth", "tokenExpiresAt?: "+ authorization.getExp());
}
mNavController.navigate(R.id.start_login); //every time authorization is changed, we check if valid else we react by prompting user to login
}
}
});
}
}
LogoutFragment.java
public class LogoutFragment extends Fragment {
private AuthorizationViewModel mAuthorizationViewModel;
private Authorization mAuth;
private FragmentLogoutBinding mBinding;
public LogoutFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mAuthorizationViewModel = ViewModelProviders.of(getActivity()).get(AuthorizationViewModel.class);
mAuthorizationViewModel.getLoggedInUserAuth().observe(getActivity(), new Observer<Authorization>() {
#Override
public void onChanged(Authorization authorization) {
mAuth = authorization;
}
});
// Inflate the layout for this fragment
mBinding = DataBindingUtil.inflate(inflater,R.layout.fragment_logout,container,false);
View view = mBinding.getRoot();
mBinding.setViewModel(mAuth);
return view;
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
new AlertDialog.Builder(getContext())
.setTitle(R.string.title_logout_fragment)
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
mAuthorizationViewModel.logoutCurrentUser(); //toggle login status, this will mutate LiveData by updating the database record then UI will react and call login fragment
}
})
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.cancel();
Navigation.findNavController(view).popBackStack();
}
})
.setOnDismissListener(new DialogInterface.OnDismissListener() {
#Override
public void onDismiss(DialogInterface dialogInterface) {
}
})
.show();
}
}
Create a getter method of mAuthorization in class AuthorizationRepository
public MutableLiveData<Authorization> getAuthorizationResult() {
return mAuthorization;
}
Then modify your AuthorizationViewModel class like below
public void init() {
mAuthorizationRepository.getLoggedInUser();
}
public LiveData<Authorization> getLoggedInUserAuth() {
return mAuthorizationRepository.getAuthorizationResult();
}
It's too late but might help someone.
I faced the same issue when I did this
MyDao myDao;
private LiveData<List<T>> liveList;
//in constructor of repo after initializing myDao;
this.liveList = myDao.getAllData();
//somewhere in repo
for(T t : liveList.getValue()){/*computation*/}
and this is how I solved it
MyDao myDao;
//in constructor of repo don't do this because called on main thread
this.list = myDao.getAll();
//in constructor of repo initialize your Dao (in this case myDao)
//somewhere in repo (must not be on main thread)
for(T t : myDao.getAll()){/*computation*/} //do this on background thread
in MyDao
#Query("SELECT * FROM myTable")
List<T> getAll();
#Query("SELECT * FROM myTable")
LiveData<List<T>> getAllData();
Or, if you are accessing the liveList in some other place (than repository) then you must set an observer for the same
I know there are similar questions but it still doesn't answer my question in the manner I need for my current situation.
I have three activity presenters that each need to call a certain data remotely which will therefore call the activity presenter back when data arrives. To create this data listener I created an interface listener and since all three Presenters ask for the same data and retrieve it, all three presenters implement this interface listener.
Interface listener:
interface ListenerInterface {
onGotData();
}
Presenter one:
class PresenterOne implements ListenerInterface{
public void getData() {
DataManager dataManager = new DataManager();
dataManager.getData(this);
}
#Override
public void onGotData(Data data) {
//Do something with data
}
}
Presenter two very similar to presenter one:
class PresenterTwo implements ListenerInterface{
public void getData() {
DataManager dataManager = new DataManager();
dataManager.getData(this);
}
#Override
public void onGotData(Data data) {
//Do something with data
}
}
Assume Presenter three is exactly the same as the previous. The data manager class:
class DataManager {
public void getData(final ListenerInterface listener) {
//Gets data
addOnCompleteListener(new OnCompleteListener<Data data > () {
#Override
public void onComplete (#NonNull DataCall < Data > dataCall) {
listener.onGotData(dataCall.getResults());
}
});
}
}
Would doing so someone call all three presenters since the interface is the one doing the calling or only call the presenter that is passed? Is there anything I should worry about if I followed way? If anyone who knows the Android framework well could provide a detailed answer so I could learn from it more that would be great.
The reason I want to do this is I want to communicate through interfaces between classes.
Sorry if this question is simple for some people but I am still learning.
Thank you very much in advance.
you can use RxBus implementation to make global event (e.g. your onGotData).
First you have to create RxBus class.
public class RxBus {
private static RxBus instance;
private PublishSubject<Event> subject = PublishSubject.create();
public static synchronized RxBus getInstance() {
if (instance == null) {
instance = new RxBus();
}
return instance;
}
private RxBus(){}
public void postEvent(Event event){
subject.onNext(event);
}
public Observable<Event> getEvents(){
return subject;
}
}
And now, you should subscribe to it in BaseActivity or something like this (depends or your project structure).
private RxBus rxbus;
private Subscription rxBusSubscription;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
rxBus = RxBus.getInstance();
}
#Override
protected void onResume() {
super.onResume();
if (shouldSubscribeRxBus()) {
rxBusSubscription = rxBus.getEvents()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(event -> {
if (event.getEventType() == Event.EventType.DATA_RECEIVED) {
onGotData(data);
}
});
}
}
Now implement you onGotData as you want.
When you catch data received call:
class DataManager {
public void getData(final ListenerInterface listener) {
//Gets data
addOnCompleteListener(new OnCompleteListener<Data data > () {
#Override
public void onComplete (#NonNull DataCall < Data > dataCall) {
RxBus.getInstance().postEvent(new GotDataEvent(dataCall.getResults()));
}
});
}
}
You can create your Event classes structure as you want.