Update RecyclerView after Dialog and orientation change - java

I have an app which uses only one activity. Inside, I load a fragment with a recyclerview.
Each item in the recyclerview is a dialog fragment. The user can change the items, therefore the recyclerview is updated when the dialog is dismissed.
The problem I'm having now is that when the app is rotated while the dialog is active, the recyclerview will not get updated after the dialog is dismissed.
My dialog gets recreated along with everything in the app when destroyed so I suspect the dismisslistener gets destroyed when recreating.
The dialog exposes a method to set the dismisslistener and then overrides the event to check if one was set.
public void setOnDismissListener(DialogInterface.OnDismissListener
onDismissListener) {
this.onDismissListener = onDismissListener;
}
#Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
if (onDismissListener != null) {
onDismissListener.onDismiss(dialog);
}
}
I then set a dismisslistener inside the onViewBindHolder of the recyclerview
editor.setOnDismissListener(dialog -> {
recycler.getAdapter().notifyDataSetChanged();
}
I'm unsure why this happens, but it seems that after changing the orientation and recreating everything, the recyclerview is never updated. I found out that the method is still called, because inside the ondismisslistener was another function that updated another view with getView().findViewById() which then crashed the app because getView returned null.
I couldn't find any other post about this here.
Does anyone know how I fix the dismisslistener or maybe any other way to wait for a dialogfragment to close?

Have you added android:configChanges="orientation|screenSize" to your activity in AndroidManifest?
Otherwise try (onRestoreInstanceState() and onSaveInstanceState()

Alright I've found out what went wrong.
When recreated, the dialog loses all his variables, just like everything else.
It could be theoretically possible to save those variables in the bundle and fetch them again on recreation, but I have not found a way to do this with an OnClickListener, since objects passed to a bundle must be serializable or parcable.
Therefore, my dialogues are now just dismissed when they do not have any variables via a check in OnCreate.

Related

The content of the adapter has changed but ListView did not receive a notification -Android

I have looked through a lot of content online but none of the suggestions work. I have a listview that sometimes work and sometimes crashes my app with the following:
The content of the adapter has changed but ListView did not receive a
notification. Make sure the content of your adapter is not modified
from a background thread, but only from the UI thread. Make sure your
adapter calls notifyDataSetChanged() when its content changes.
I am definitely calling the notifyDataSetChanged() (In the onPostExecute() of an async task). I have tried as suggested by examples online to run it on the main thread like below:
getActivity().runOnUiThread(new Runnable() {
public void run() {
CAdapterFilter.notifyDataSetChanged();
}
});
but that did not work either. Still my app will crash at random times. Can anyone shed some light here? Why is it only crashing randomly and not every time. What am I missing?
Try this :-
doInBackground(....) {
mPseudoList.addAll(fetched_list);
return xyz;
}
onPostExecute(...) {
mAdapterList.addAll(mPseudoList);
mAdapter.notifyDataSetChanged();
}
Change the adapter list reference in onPostExecute(...) and then notify the adapter !!
Note :-
In doInBackground dont update the list whose reference the adapter holds , instead use a pseudo-list and update the adapter reference list in onPostExecute

Portrait layout doesnt work when activity starts in landscape

This is a scaled down version of the actual problem. To recreate the difficulty I am facing.
I have taken the example from the official website developer.android.com to cite my problem.
Building a Flexible UI
MainActivity has 2 layouts. One is for the default(portrait in small screen devices) layout in the layout folder. The other layout for both large-screen and landscape mode, kept in layout-large and layout-land folder.
Default layout for activity_main.xml contains only one FrameLayout (R.id.fragment_container) in which I add and replace 2 fragments that I create, dynamically.
The other layout is same for both the layout-land and layout-large folders. It has 2 static fragments [R.id.headlines_fragment - to display a list of headlines] and [R.id.article_fragment - to display the details when headlines are selected]. Horizontally placed. One on the left to show the lists and the one on the right to show details.
This is the code for MainActivity.java which controls all the fragments :
public class MainActivity extends Activity implements OnHeadLineSelectedListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.news_article);
if(findViewById(R.id.fragment_container) != null) {
if(savedInstanceState != null) {
return;
}
HeadlinesFragment firstFragment = new HeadlinesFragment();
firstFragment.setArguments(getIntent().getExtras());
getFragmentManager().beginTransaction().add(R.id.fragment_container, firstFragment).commit();
}
}
#Override
public void onArticleSelected(int position) {
ArticleFragment articleFrag = (ArticleFragment) getFragmentManager().findFragmentById(R.id.article_fragment);
if(articleFrag != null && articleFrag.isVisible()) {
articleFrag.updateArticleView(position);
} else {
articleFrag = new ArticleFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction().replace(R.id.fragment_container, articleFrag);
transaction.addToBackStack(null);
transaction.commit();
}
}
}
As soon as the activity is started, I check if fragment_container that is the FrameLayout is present or not. If it is not present, then the layout with the 2 fragments has been loaded. Hence no need to add the fragments dynamicaly as they are already present.
Otherwise, I check if the savedInstanceState is null or not. If null, then I create a new HeadlinesFragment and add it to the frame. If it is not null, then it means that the activity has already been created previously, and hence the HeadlinesFragment must have been added already. No need to add it again. So, return.
And onArticleSelected() method replaces the existing fragment in the frame with the ArticleFragment, or if in the other layout, it simply updates the fragment as it is already present. It is called from the HeadlinesFragment when an item is selected.
Now, this all works perfectly well if we enter the activity in portrait mode and then change the orientation. No problem. Flawless.
But if we enter the activity in the landscape mode, as soon as I change the orientation to the portrait mode, a blank screen is shown.
The reason being, the onCreate() is called, and the savedInstanceState returns as not null. Hence, the HeadlinesFragment is not created and added to the frame.
And yes, if I remove that check, then the app works fine, but that will mean that a new HeadlinesFragment is created and added to the frame each time and gets stacked on top of eachother. Which is not at all desirable.
I cannot implement this by just finding out the orientation and applying the appropriate layout. Because, in large-screen devices, even if it is in portrait mode, it is supposed to show both the fragments at once.
I have tried many convoluted logic. But nothing seems to work. Any help is appreciated.
Entering activity in portrait mode
1> List Items are shown.
2> Clicking items replaces the fragment with the ArticleFragment (details).
3> Changing the orientation, shows both side by side. Everything works.
--->
Entering activity in landscape mode
1> Both the list and details are shown. Everything works.
2> But as soon as the orientation changes, you get the blank screen. As Headlines fragment is not created and added.
-->
It would be really helpful if someone could guide me as to how I can solve this problem. And as the actual project is huge, and this logic has already been implemented, a drastic change in logic is not an option anymore as that will mean re writting thousands of lines of code. Thank you. :)
Ok. I got the problem. The problem is we are using Fragments in xml layouts in large devices.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment android:name="com.example.android.fragments.HeadlinesFragment"
android:id="#+id/headlines_fragment"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent" />
<fragment android:name="com.example.android.fragments.ArticleFragment"
android:id="#+id/article_fragment"
android:layout_weight="2"
android:layout_width="0dp"
android:layout_height="match_parent" />
</LinearLayout>
So android is trying to catch the Fragments in savedInstanceState in MainActivity. When screen rotates, system tries to restore the above Fragments even though different layout loads in potrait mode. And so system considers that the article_fragment is also available on the right side and it tries to update it on click on the Headline.
So, What's the solution ?
I have changed a little code in MainActivity and nothing else :-)
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(null);
setContentView(R.layout.news_articles);
// Check whether the activity is using the layout version with
// the fragment_container FrameLayout. If so, we must add the first fragment
if (findViewById(R.id.fragment_container) != null) {
// However, if we're being restored from a previous state,
// then we don't need to do anything and should return or else
// we could end up with overlapping fragments.
/*if (savedInstanceState != null) {
return;
}*/
// Create an instance of ExampleFragment
HeadlinesFragment firstFragment = new HeadlinesFragment();
// In case this activity was started with special instructions from an Intent,
// pass the Intent's extras to the fragment as arguments
firstFragment.setArguments(getIntent().getExtras());
// Add the fragment to the 'fragment_container' FrameLayout
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, firstFragment).commit();
}
}
So what I have done just I told the system that I don't want any thing restored by using super.onCreate(null); so its restoring nothing now.
About the blank screen you are getting
Whenever you start activity in landscape mode. It loads large display by default. Without entering into if satement. Because it can't get fragment_container in landscape mode. And then you rotate screen to load portrait layout and system gets savedInstanceState != null and it returns without loading HeadlinesFragment. So you get bank screen.
So I have commented If statement as you can notice.
/*if (savedInstanceState != null) {
return;
}*/
So now It load everything correctly.
No issue
Download the code sample from this Developer site and modify it according to your needs.
Build Dynamic UI with fragments
Its super and reliable code.
I'm sure there are reasons why the Fragment was rendered correctly in Portrait -> Landscape and not in the reverse but one thing is very clear about the Activity lifecycle
Called when the activity is starting. This is where most
initialization should go: calling setContentView(int) to inflate the
activity's UI, using findViewById(int) to programmatically interact
with widgets in the UI, calling managedQuery(android.net.Uri,
String[], String, String[], String) to retrieve cursors for data
being displayed, etc.
Note the part that says This is where most initialization should go. By exiting after checking that savedInstanceState is null, you're leaving it up to the super class to restore your Fragment which is not a good idea considering the previous view was destroyed.
My advice, inspect the content of savedInstanceState instead of just checking if it is null. Ensure that it contains enough data to restore your previous state, if not initialize your Fragment.
The best practices with Fragments involve implementing all necessary methods to monitor the state.

How to make a fragment refresh and load afresh on back pressed android

I am in a peculiar situtation in my app.
When i app first loads there is a custom listview which is populated with data from the server.I am also using a class which contains different fields for the string data from the server.
When i click an item on the custom listview,the object of the corresponding class is passed onto the next fragment.
That is the current fragment is replaced with a new fragment and the object is passed with bundle.
Now a new listview loads with different tasks.On clicking a task a new fragment with a camera is loaded.
After taking the image and uploading to server, the status in the JSON changes to "COMPLETED".But now when i press back the old listview is shown.
Is there a way to populate the listview on back pressed with new data?
The issue is that I am passing an object right from the first fragment.
Now i need a new object on back pressed,how to pass the new object on back pressed?
When Fragment 2 gets the data, it should pass it along at some point before Fragment 1 is woken.
There are almost a half dozen ways to pass data, and the best way depends on a number of factors like who should own the lifecycle of the data, data pull vs push, dependency between fragments, do multiple components need updating, etc.
I'm just going to advise to simply cache the data on the activity until you learn more about the different methods.
//Fragment 2 puts data to activity
((MyActivity) getActivity).mListViewData = listViewData;
Then the next part of the question is how does fragment 1 get the data. Fragment 1 is hibernating on the backstack. When it wakes up it will call the onViewCreated() method (because it's previous view was destroyed before being placed on the backstack).
In that method, we check if there's new data waiting for Fragment 1.
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
MyDataType listViewData = ((MyActivity) getActivity).mListViewData;
if(listViewData != null){
//setData is your own function for replacing the adapters
//data backing
listView.getAdapter().setData(listViewData);
}else{
listView.getAdapter().setData(...defaultData);
}
listView.getAdapter.notifyDataChanged();
}
Override the onBackPressed in the Activity that manages the Fragments. In it you can check if the fragment is visible or not (the one from which an action should be performed if the back is pressed) and then execute your action.
#Override
public void onBackPressed(){
Fragment myFragment = getFragmentManager().findFragmentByTag("MY_FRAGMENT");
if (myFragment.isVisible()) {
String json = myFragment.getJsonData(); //update it locally
if(isUpdated){
Fragment listFragment = getFragmentManager().findFragmentByTag("MY_LIST_FRAGMENT");
listFragment.updateListView(json); //Add this method on your fragment
}
}
super.onBackPressed();
}
Obs.: To use the .findFragmentByTag() you should add tags once you're making the transaction like so:
fragTrans.replace(android.R.id.content, myFragment, "MY_FRAGMENT");
If, for any reason the listFragment has been cleaned from memory, you would have to reload the data anyway so just download the new data again.
To update the ListView please see: How to refresh Android listview? . Note thought that you will need to will need to send a new data set to the list view (which you can do inside the updateListView() method)

Error in using a checkbox between multiple activities

I am trying to write a settings activity in an application on Eclipse. In the Main Activity, it has a button that runs a certain command. In the settings activity, I want to have a checkbox that when checked, changes what the button in the Main Activity runs when it is tapped. Right now, I have it so that when the checkbox is checked, it changes the value of a boolean and passes it to the main activity. When the button in the main activity is tapped, it checks to see if the boolean is true or false. All of this works perfectly, but when I return to the settings activity after that, the checkbox is unchecked. What should I do to have it stay checked after I go to another activity?
I believe the comment I posted is the answer:
You need to save the state of the activity. This information can be found at Saving Android Activity state using Save Instance State but in short you need to override these two methods:
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
}
and
#Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
}
you can use shared preference in android to store state. take look at this
http://developer.android.com/guide/topics/data/data-storage.html#pref
and
http://www.androidhive.info/2012/08/android-session-management-using-shared-preferences/

Iconsistent results when returning to a previous activity

I have an android app that has various activities.
for example there is a home screen and an update screen.
The home screen is a list activity.
A button on the home screen brings you to the update screen that updates the database from a server. In theory when Returning to teh homescreen after an update, the list should be changed to reflect the update just done.
the code for going to the update screen is as follows:
Button synch = (Button) findViewById(R.id.synchButton);
synch.setOnClickListener(new OnClickListener() {
public void onClick(View viewParam) {
Intent intent = new Intent(HomeScreen.this, SynchScreen.class);
startActivity(intent);
}
});
and the code for returning back to the homescreen after the updates is:
main_menu.setOnClickListener(new OnClickListener() {
public void onClick(View viewParam) {
finish();
}
});
the list is compiled from an async task that runs in onStart, so my understanding is that onStart should run when I return to the homescreen, thus always displaying the most up to date version of the list.
On my Emulator I always get teh updated list, but when I run it on my phone the list is never updated, it just returns to the state of teh screen before I did the update.
any ideas?
thanks
Kevin
Check the Activity lifecycle section of the Android documentation. The code updating the view should probably be moved to onResume, since the Activity might not get killed when launching a new one.
Put the code for starting the Asynctask in onResume. Read the documentation related to activity life cycle.
#Override
public void onResume() {
super.onResume();
Apaptor.refreshListView(Vector);
}

Categories