How to restore search results in fragment? - android

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.

Related

How to cache PagingData in a way that it does not cause its data to reflow

I have a simple setup of 2 fragments: ConversationFragment and DetailsFragment
I am using Room with Paging 3 library and to populate the ConversationFragment I am using a PagingLiveData implementation together with a AndroidViewModel belonging to the ConversationFragment.
I am not using the Navigation Components here, just a common fragment navigation as per Android documentation.
From that fragment I can open the DetailsFragment and then return back to the fragment again. Everything is working well, until I open said fragment and return, then the observer that was tied in the ConversationFragment is lost since that fragment is being destroyed when opening the DetailsFragment.
So far this is not a big issue, I can restart the observer again and it does work when I do that.
However, when I attach the observer again the entire list reflows, this causes the items in the RecyclerView to go wild, the position the list was on is lost and the scrollbar changes sizes which confirms pages are being loaded/reloaded.
I could withstand the weird behavior to a degree, but to have the position lost on top of that is not acceptable.
I looked into caching the results in the view model, but the examples I could find in the available documentation are basic and do not show how the same could be achieved using a LiveData<PagingData<...> object.
Currently this is what I have:
ConversationFragment
#Override
public void onViewCreated(
#NonNull View view,
#Nullable Bundle savedInstanceState
) {
if (viewModel == null) {
viewModel = new ViewModelProvider(this).get(ConversationViewModel.class);
}
if (savedInstanceState == null) {
// adapter is initialized in onCreateView
viewModel
.getList(getViewLifecycleOwner())
.observe(getViewLifecycleOwner(), pagingData -> adapter.submitData(lifecycleOwner.getLifecycle(), pagingData));
}
super.onViewCreated(view, savedInstanceState);
}
ConversationViewModel
public class ConversationViewModel extends AndroidViewModel {
final PagingConfig pagingConfig = new PagingConfig(10, 10, false, 20);
private final Repository repository;
private final MutableLiveData<PagingData<ItemView>> messageList;
public ConversationFragmentVM(#NonNull Application application) {
super(application);
messageList = new MutableLiveData<>();
repository = new Repository(application);
}
public LiveData<PagingData<ItemView>> getList(#NonNull LifecycleOwner lifecycleOwner) {
// at first I tried only setting the value if it was null
// but since the observer is lost on destroy and the value
// is not it would never be able to "restart" the observer
// again
// if (messageList.getValue() == null) {
PagingLiveData.cachedIn(
PagingLiveData.getLiveData(new Pager<>(pagingConfig, () -> repository.getMessageList())),
lifecycleOwner.getLifecycle()
).observe(lifecycleOwner, messageList::setValue);
// }
return messageList;
}
}
As it is, even if I return the result of the PagingLiveData.cachedIn the behavior is the same when I return to the fragment; the items show an erratic behavior in the recyclerview list and the position it was on is totally lost.
This is what I was trying to achieve to see if it fixed my issue:
This is a code lab available here: https://developer.android.com/codelabs/android-training-livedata-viewmodel#8
As you can see the mAllWords are cached and they are only initialized when the view model is constructed for the first time, any subsequent changes are simply updates and would only require new observers to be attached when the fragment is destroyed and created again while still in the back stack.
This is what I was trying to do, but it does not work the way I thought it did, at least it is not as straight forward as I thought.
How can this be achieved?
There's quite a lot to unpack here but my best guess would be your getList method in ConversationViewModel. You're on the right track with using ViewModels and LiveData to persist data across navigation but here you're recreating the LiveData every time this method is called, meaning when you resume ConversationFragment and onViewCreated is called, it creates a new Pager which fetches new data.
The solution would be to create the pager when ConversationViewModel is first created and then accessing the LiveData object itself, rather than the method. You can see this in the Codelab example, they assign the LiveData in the constructor and simply return the already created LiveData in the getAllWords() method.
I'm using this as an example, change ConversationViewModel to something like this and change it to use your config and repository.
private final LiveData<PagingData<ItemView>> messageList;
public ConversationFragmentVM(#NonNull Application application) {
super(application);
repository = new Repository(application);
// CoroutineScope helper provided by the lifecycle-viewmodel-ktx artifact.
CoroutineScope viewModelScope = ViewModelKt.getViewModelScope(viewModel);
Pager<Integer, User> pager = Pager<>(
new PagingConfig(/* pageSize = */ 20),
() -> ExamplePagingSource(backend, query));
messageList = PagingLiveData.cachedIn(PagingLiveData.getLiveData(pager), viewModelScope);
}
public LiveData<PagingData<ItemView>> getList(){
return messageList;
}
Then in your fragment, you simply observe getList() like usual, except this time it's returning a prexisting version.
viewModel.getList().observe(getViewLifecycleOwner(), pagingData ->
adapter.submitData(lifecycleOwner.getLifecycle(), pagingData));
}
I haven't been able to test that this compiles or works so let me know if it doesn't and I'll update this answer.

How to destroy variables instantiated in fragment after user logs out

I have to pull data from server for the first time when user lands on the fragment and then data should persist in the application unless user logs out but I tried this way.
public class AttendanceFragment : Fragment
{
private static ListView listView;
private static ProgressBar progress;
private static List<DA_ClassSectionAttendance> dataList=new List<DA_ClassSectionAttendance>();
// If i instantiate this variable 'dataList' here
//it will be persisted even the user logs out I know its declared as static
// because I am accessing this variable on broadcast receiver.
// But I want this re-instantiated after user logs out but HOW?
private static AttendanceListAdapter attendanceAdapter;
private static DA_Attendance daAttendance;
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// dataList = new List<DA_ClassSectionAttendance>(); if I instantiate this variable here everytime this fragment created or restores dataList.Count is zero or null
attendanceAdapter = new AttendanceListAdapter(this.Activity, dataList);
if((dataList==null || dataList.Count==0)) // pull data from server for the first time when fragment is created but I want this method call when user logs out as well.
{
GetClassSection(); // this method pulls data from server
}
//set whether MenuOption show/hide from toolbar
HasOptionsMenu = true;
}
Thank you
You can release the variables in onDestroy of the fragment. If you need to persist the data then you need to save it in DB. You can use SQLlite or realDB based on your requirement. Then when user logs out, clear the DB at that time. Hope it clears

Yet another Fragment Communication

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.

Passing searching data to Searchable Activity

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.

How to maintain the data in fragments Android

I have one Activity having 5 buttons: button1, button2, button3, button4, button5.
button1 clicks----open fragment1
button2 clicks----openf ragment2
button3 clicks----open fragment3
button4 clicks----open fragment4
button5 clicks----open fragment5
In fragment1 I am downloading data and displaying in a customized listview. In fragment2 I am downloading data and displaying in edittexts, textviews..etc.
But if I click the button1 again data is downloading again. I want to show the same view where the user comes back from fragment1 to fragment2 by clicking buttons.
How can I reach this logic? Please help me in this. If you want any information I will provide.
Thank you in advance!
EDIT : I need google chrome tab functionality in android fragments.here tabs are fragments.if you open one website in google search and open onother page in anothe tab.if you can navigate to first tab you can see the opened one only.in my case i am starting from the scratch of the fragment.how to reach chrome tab functionality in android fragments.
There are many ways to go around it. whatever data you have download you can store it either temporarily or permanently. if you want to store it temporarily you can:
use an ArrayList<HashMap<String,String>> object to store it.
or permanently using either
SQLiteDatabase or
SharedPrefrences
depending on the data type.
Now whenever you open your fragment you can check if the data already exists if not you hit the Service and get the data otherwise you can read directly from the source you have chosen.
I would use the Model-view-controller architecture on this app.
This means create a class to store this data. Use the singleton pattern when designing the class. First time the user press Button 1, upon creation of the fragment, call a method from the Model class to check if the data is downloaded or not. If it's not downloaded then fetch it, store it in the class and display it. If data is downloaded then use an method from the Model class to get the data and display it.
If you have small a amount of data to display you can use bundles or intents to store it.
UPDATE:
Below is a simple example of a singleton class that can be used to store your data and find out if your class contains initialized data or not. In my example I used as data an int value but you are free to use whatever type you want, even a class.
public class SingletonExample {
private static SingletonExample mSingleton = null;
private int mMyData;
private boolean mDataInitialized;
private SingletonExample() {
mDataInitialized = false;
}
public static SingletonExample getInstance() {
if (mSingleton == null) {
mSingleton = new SingletonExample();
}
}
return mSingleton;
}
public boolean isDataInitialized() {
return mDataInitialized;
}
public void setMyData(int myData) {
mMyData = myData;
mDataInitialized = true;
}
public int getMyData() {
return mMyData;
}
}
The singleton call you call like this:
SingletonExample mDataBank = SingletonExample.getInstance( );
mDataBank.setMyData(0);

Categories

Resources