I have an activity with a fragment. Inside that fragment there is a viewpager and inside that there is a list. Now once the user clicks on an item in the list the fragment should be replaced with another fragment and I need to pass some data like the list position and some other values linked to that. I can implement this by using interfaces, but as we are using rxjava so want to do it using rx... Don't want to implement the event bus or rxbus pattern right now. So how do I implement it using rxjava?
One way to do it:
/* inside whatever you mean by the list */
PublishSubject<Void> mClickSubject = PublishSubject.create(); //or use another type instead of Void if you need
/*...*/
item.setOnClickListener(v -> mClickSubject.onNext(null));
/*...*/
public Observable<Void> itemClicked() {
return mClickSubject;
}
/* pass your subject/observable all the way to the activity */
/* inside the activity */
private void setupSubscription() {
mCurrentFragment.listItemClicked()
.subscibe(/* switch fragment */);
}
Or another way is to have a singleton / static class holding a member PublishSubject and push items through it. Doing it like this you won't need all the getters to pass the observable from the list to the activity.
Related
Im trying to learn view models and implement them in my app. I have been following a tutorial on getting me started but, I have a couple questions.
How do i listen for a button click? Since all the business logic is suppose to be stored in the view model would I put an OnClick listener there? Or would i put it with my onChange method in the activity that launches the fragment?
How to tell the fragment to use the view model?
Update was looking at this guys tutorial Location of click event in MVVM architecture . Isn't the whole point of mvvm to eliminate the need of interfaces?
Update 2: Found where you can use data binding to shove OnClick listener into button here: Handle onClick event with Databinding and MVVM and Using DataBinding library for binding events
Live data observe code from activity launching fragment
//private BattleRhythmViewModel battleModel;
battleModel = ViewModelProviders.of(this).get(BattleRhythmViewModel.class);
battleModel.getEvents().observe(this, new Observer<ArrayList<Event>>() {
#Override
public void onChanged(#Nullable ArrayList<Event> events) {
// Add newly created events to array/recycler view
// Another one for pushing new platform/content to database
}
});
}
View model for fragment
public class BattleRhythmViewModel extends ViewModel {
private MutableLiveData<ArrayList<Event>> battleRhythmEvents;
private MutableLiveData<ArrayList<TableData>> battleRhythmExtra;
public LiveData<ArrayList<Event>> getEvents()
{
return battleRhythmEvents;
}
public LiveData<ArrayList<TableData>> getExtras()
{
return battleRhythmExtra;
}
}
In a word game app I share a model between an activity and a fragment:
public class MainViewModel extends AndroidViewModel {
private LiveData<List<Game>> mGames;
private final MutableLiveData<Game> mDisplayedGame = new MutableLiveData<>();
(please excuse the non-english text in the screenshot)
The activity observes mGames being currently played by the user and updates the navigational drawer menu (see the left side of the above screenshot).
The fragment observes mDisplayedGame and displays it in a custom view (see the right side of the above screenshot).
My problem is that when the list of the games is updated at the server (and the activity receives new list of games via Websocket and stores it in the Room), I need to post an update to the fragment: "Hey, the game you are displaying was updated, redraw it!"
Is it possible to do that from within the shared view model?
I know that I could observe mGames in the fragment too and add a code there iterating through them and then finding out if the displayed game was updated at the server.
But I would prefer to do it in the MainViewModel because I have a feeling that the fragment should only observe the one game it is displaying and that's it.
TL;DR
Whenever mGames is updated in the view model via Room, I need to notify the mDisplayedGame observers too!
You should use a MediatorLiveData for this.
The way it works is that
public class MainViewModel extends AndroidViewModel {
private final LiveData<List<Game>> mGames;
private final MutableLiveData<Game> mSelectedGame = new MutableLiveData<>();
private final MediatorLiveData<Game> mDisplayedGame = new MediatorLiveData<>();
{
mDisplayedGame.addSource(mGames, (data) -> {
// find the new value of the selected game in the list
mSelectedGame.setValue(newSelectedGame);
});
mDisplayedGame.addSource(mSelectedGame, (data) -> {
mDisplayedGame.setValue(data);
});
}
And then you expose mDisplayedGame as a LiveData<Game> and it should just work.
Use callback bro
-add a callback interface in your viewmodel and a setCallback method
-make your fragment implement it then call viewmodel.setCallback(fragment)
-call the callback in your obsever
I have a fragment X which indeed has a RecyclerView, X has a search view, I use the search view to search something and filter the RecyclerView into few rows. After the filtering, if user clicks on some row, it goes to another fragment say Y. From there if the user clicks back it comes back to X. My task is that X should persist the search results after this coming back. What is the best approach to achieve this?
You can use a the singleton pattern to store the data!
E.g.
// DataManager.java
public class DataManager {
private static DataManager thisInstance;
// Declare instance variables
List<String> searchResultItems;
public static DataManager getSharedInstance() {
if (thisInstance == null) {
thisInstance = new DataManager();
}
return thisInstance;
}
private DataManager() {
searchResultItems = new ArrayList<>();
}
public List<String> getSearchResultItems() {
return searchResultItems;
}
public void setSearchResultItems(List<String> searchResultItems) {
this.searchResultItems = searchResultItems;
}
}
Now you can store and retrive data from everywhere:
// Setter
DataManager.getSharedInstance().setSearchResultItems(items);
// Getter
List<String> items= DataManager.getSharedInstance().getSearchResultItems();
Propertly override onSaveInstanceState in Fragment so that it will store search input - filter. Also override onCreate in such way it will apply saved filter on your RecyclerView.
Before navigating to another fragment, obtain Fragment.SavedState via FragmentManager and save it temporary in Activity which hosts your fragments. Note, this state can be lost if you do not properly save Activity state due of configuration changes (rotate) = you have to override also onSaveInstanceStatein Activity. Or simply save Fragment.SavedState in global scope (some static field, or in Application).
When navigating back to previous fragment, re-create fragment from Fragment.SavedState i. e. invoke Fragment#setInitialSavedState(Fragment.SavedState).
For more details see my research on similar topic.
There are a lot of questions about fragment communication here, but they are normally question about getting data from activity and sending data back to activity, normally starting from fragment.
But I wonder what what is best approach for sending data from activity to fragment, when you cannot do it when creating fragment? For clarification, Lets assume that an app has 2 fragments that can use (can not must) some data to improve user experience, but obtaining this data is costly. So obtain this data in activity using a Loader or AsyncTask in main activity while creating Fragments themselves. Now when data is ready asynchronously in Activity, we need to send this data to Fragments. What is best approach for this? I thought of a way for doing this, and I like to know if there is any problem with this approach.
1-In fragment we use onAttach to send fragment to activity and check if any data is already read:
#Override
public void onAttach (Activity activity) {
MyActivity act = (MyActivity)activity;
act.addFragment(this);
Data data = act.getData();
if (data != null) {
setAdditionData(data)
}
}
2-and in activity store a WeakReference to Fragment:
private ArrayList<WeakReference<Fragment>> mFragments = new ArrayList<>();
...
public void addFragment(Fragment frag) {
WeakReference<Fragment> f = new WeakReference<Fragment>(frag);
mFragments.add(f);
}
public Data getData() {
return mData;
}
public void updateFragmentsData() {
for (Iterator<WeakReference<Fragment>> iterator = mFragments.iterator(); iterator.hasNext();) {
WeakReference<Fragment> wf = iterator.next();
Fragment f = wf.get();
if (f != null) {
f.setAdditionData(mData);
} else {
iterator.remove();
}
}
}
Now when fragments attaches, it adds itself to list of fragments in activity and checks if data is already ready and if ready it will use that data. On the other hand, when data is ready asynchronously in activity, it can call updateFragmentsData() to update all fragments data.
I wonder if this approach is correct or it can be incorrect in some situations? Any idea? Is there any better approach for notifying fragments from main activity?
Btw, is it possible to use Handler/Message for communicating between fragments too or not? As another approach?
Best Regards
I can think of three ways.
Use a listener. Write an interface in the activity to use it as a listener. The fragment implements the interface and registers and unregister as a listener at appropriate time.(say at onCreateView and onDestroyView).
This one is my favorite. I hope DataBinding is gaining popularity and it can be used to solve this. Say you define a particular model for the fragment layout. Now you use ObservableFields in the model. Pass this model to your databinding variable. Now change this object from either the activity or the fragment itself, changes will be reflected in the view.
The newly introduced ViewModels. I will be using them from my next project.
I have a main activity which has 2 fragments. The main activity has a SearchView in the action bar. Both the fragments have a list of large number of strings, List<String>.
The flow is:
User enters Fragment I --> Selects a string (lets say Selection1) --> Based on Selection1 a list of strings is populated in the second fragment --> Here the user selects a second String ---> Processing based on these two strings.
Now since both the fragments contain a large number of strings, the user enters a query in the SearchView, which filters the list and reduces it to a smaller list displayed in the SearchableActivity.
Now the problem is how does the SearchableActivity get access to these two List<String> to filter them based on the query and display a reduced list to the user.
Currently what I have done is overridden onSearchRequested and pass the data as
#Override
public boolean onSearchRequested()
{
Bundle appData = new Bundle();
appData.putString(FRAGMENT_ID, "Fragment_A");
appData.putStringArrayList(SEARCH_LIST, searchList);
startSearch(null, false, appData, false);
return true;
}
Is there a better way or standard way by which this problem can be handled i.e. an implementation that allows data to be based from my MainActivity to SearchableActivity?
Edit: Adding code. Showing how data is set in the Fragment. onDataReceived is called from the HttpManager which receives the data.
#Override
public void onDataReceived(String type,final Object object)
{
switch(type)
{
case PopItConstants.UPDATE_LIST:
getActivity().runOnUiThread(new Runnable() {
#Override
public void run()
{
updateCinemaList((List<String>) object);
}
});
break;
}
}
public void updateDataList(List<String> data)
{
this.dataList = data;
spinner.setVisibility(View.GONE);
mAdapter.updateList(dataList);
}
I just answered a similar question a few minutes ago, at how can I send a List into another activity in Android Studio
I encourage you to rethink your pattern of simply passing data around among Activities and Fragments. Consider creating one or more data models (non-Android classes) for your application, and making these models available to the Android classes (Activities, Fragments, etc.) that need them.
Remove all of the data storage and manipulation code from your Activities and Fragments, and put it into the model(s).
Okay... So this is how I did it.
Basically, the data received in the two fragments was not simply List<String> but they were models viz. Cinema and Region which contained details other than names including location, rating etc.
So, firstly, I made an interface ISearchable
public Interface ISearchable
{
// This contains the Search Text. An ISearchable item is included
// in search results if query is contained in the String returned by this method
public String getSearchText();
//This is meant to return the String that must be displayed if this item is in search results
public String getDisplayText();
//This is meant to handle onClick of this searchableItem
public void handleOnClick();
}
Both the Cinema and Region models implemented ISearchable.
After this, I used a singleton class DataManager in which I maintained a List<ISearchable> currentSearchList.
public class DataManager
{
.....<singleton implementation>....
List<ISearchable> currentSearchList;
public void setSearchList(List<ISearchable> searchList)
{
this.currentSearchList = searchList;
}
public List<ISearchable> getSearchList()
{
return this.currentSearchList;
}
}
So whenever a fragment (either Fragment_A or Fragment_B) is loaded, it updates this currentSearchList, so that when the SearchableActivity performs search all it has to do is DataManager.getInstance().getSearchList() and then use this list for filtering out a list of matching items.
This is how I handled the problem of having Lists in Activity other than the SearchableActivity using which search needs to be performed.
I understand this might not be the best solution, so, I look forward to suggestions and criticisms, and using that to be arrive at a better solution.