i saw a lot of posts saying that you should put your async task inside a Fragment(i dont remember other ways), so you can rotate screen and keep it...
but its not working for me
Activity shows the Fragment;
MyGetJsonFragment frag= ....
frag.setRetainInstance(true);
fm = getSupportFragmentManager();
ft = fm.beginTransaction();
ft.replace(R.id.fragment_main, frag);
ft.commit();
than, inside this Fragment i have a RecyclerView, and when the user click in any item on list, it call the Adapter and inside the adapter onBindViewHolder i implemented the onClick to call the AsyncTask
holder.button_get.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
new AsyncGet(activity, AdapterList.this).execute();
}
});
if i use the configChanges ( android:configChanges="orientation|keyboardHidden|screenSize)
at manifest it works normal, but i heard and read that this is not a good workaround!
after removed the configChanges, if i rotate the screen, the progress dialog dismiss and than on the post execute i get the error at progressdialog.dissmiss or anyother thing that dependes the activity
i understood the activity life cycle that when i rotate the original that started the async is destroyed and new one is created...
but how can i handle it and still show the progressdialog?
i need the Context/Activity at the async to start the progress and i have some db methods inside the postExecute that use Activity/Context
and why setRetainInstance(true) is not holding the Instance?
Related
I am extremely new to Android development. I basically have an activity with some buttons, for example "seeTreePicture" and "seeSeaPicture". When I press a button, I want to use a fragment I called "ContentViewer" to display a random tree/sea picture, and also have buttons under the picture to destroy the ContentViewer fragment instance and go back to the menu. The issue is, if I try to use a Fragment Transaction anywhere other than onCreate() of the activity, I get a null pointer exception when I try to access the view in the fragment.
My activity and things related to the fragment:
public class SeeActivity extends AppCompatActivity {
DisplayFragment displayFragment;
Button seeTreeButton;
Button seeSeaButton;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_see);
seeTreeButton = findViewById(R.id.seeTreeButton);
seeSeaButton = findViewById(R.id.seeSeaButton);
displayFragment = new DisplayFragment();
seeTreeButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.add(R.id.fragmentContainer, displayFragment);
fragmentTransaction.commit();
fragmentTransaction.addToBackStack(null);
displayFragment.changeImage(randomTree);
}
});
}
}
In my fragment, change image simply changes the image source of the ImageView:
public void changeImage(int treeResource)
{
img = getView().findViewById(R.id.imageView);
img.setImageResource(treeResource);
}
I get a null pointer exception for trying to access the view from getView() in the fragment, meaning that onCreateView wasn't invoked. Yet if I put the same transaction in the onCreate() method of the activity, it works. What am I doing wrong?
The issue with your code is that you are accessing getView() of your fragment before the fragment has gone through the initialization of the view. The reason why you don't have the crash when you execute the transaction in your activity's onCreate() method is that by the time you click on a button your fragment has already gone through onCreateView() and initialized its view. Check out fragment lifecycle guide and bear in mind that you should not access your fragment view before it was created or after it was destroyed. For more information about why your fragment view is not initialized instantly check out this guide.
As for the solution, consider setting arguments for your fragment before adding it to your transaction like here:
Bundle args = new Bundle();
args.putInt(DisplayFragment.IMG_RESOURCE_ARG, randomTree);
displayFragment.setArguments(args);
Then in your onCreateView() or onViewCreated() methods of your fragment restore the arguments like here and set the image resource:
#Override
public void onViewCreated(#NonNull View view, Bundle savedInstanceState) {
int resourceId = requireArguments().getInt(IMG_RESOURCE_ARG);
img = view.findViewById(R.id.imageView);
img.setImageResource(resourceId);
}
Because the fragment transaction doesn't occur instantly. It occurs async. So the actual work of creating views hasn't occurred yet. Your options are to either use commitNow() instead of commit (which will do it synchronously, but require much more time and possibly cause your app to visibly pause) or to wait for the fragment transaction to actually complete. That can easily be done by putting it in a Runnable and passing that runnable to runOnCommit
After deep dive into stackoverflow to find similar situation and no luck I decided to post my question. For my android app I have a settings (MySettings.java) PreferenceFragmentCompat which in turn on button click popups a list (MyList.java) ListFragment and after the item choose saves it and closes the list fragment. The fragments switch implemented as follow:
MySettings.java
#Override
public void onResume() {
super.onResume();
loadData();
}
private void loadData() {
// data load from SharedPreferences
}
...
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(android.R.id.content, MyList.class).addToBackStack().commit();
MyList.java
public void onListItemClick(ListView listView, View view, int position, long id) {
// data save into SharedPreferences
...
getFragmentManager().popBackStack();
}
So, after the list fragment is closed and it's returns back to settings fragment I want to show (load) previously saved item, namely I added onResume() (suggested in hier) method in turn triggers loading data function. But loading process is not triggered after .popBackStack()
What I'm doing wrong?
Is it generally possible to achieve this for settings concept with usage of fragments? Or there is better way? If so, please share your experience(s).
Any help would be appreciated!
onResume refers to the Activity and is only called when the Activity is resumed. As you use ft.replace the preference fragment is recreated, when calling popBackStack().
Thus you can put the loadData() in the onCreateView() method of your preference fragment.
I'm very new to Androind, trying to figure how fragment and activity should be work together. I have a very ugly layout. 1 activity and 1 "root" fragment. When user click on left menu fragment are replaced by fragment manager.
expandableList.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() {
#Override
public boolean onGroupClick(ExpandableListView expandableListView, View view, int i, long l) {
LeftMenuItem group = groups.get(i);
String fragmentTag = group.getFragmentTag();
if (fragmentTag.equals(Fragment1.TAG)) {
Fragment1 fragment = (Fragment1) currentFragmentManager.findFragmentByTag(Fragment1.TAG);
if (fragment == null) {
fragment = new Fragment1();
}
FragmentTransaction ft = currentFragmentManager.beginTransaction();
ft.replace(R.id.root_frame, fragment, Fragment1.TAG);
ft.commitAllowingStateLoss();
} else if (fragmentTag.equals(Fragment2.TAG)) {
I assume code above is supposed to replace current fragment with new one. Fragments are always null actually. I do not know why.
In onCreateView of RootFragment Fragment1 is created by default.
if (savedInstanceState == null) {
Log.d(TAG, "savedInstanceState is null, creating Framgent1");
Fragment1 fragment = new Fragment1();
FragmentTransaction ft = currentFragmentManager.beginTransaction();
ft.replace(R.id.root_frame, fragment, Fragment1.TAG);
ft.commitAllowingStateLoss();
}
In onCreateView of rootFragment, rootFragment replaces itself with another Fragment1, which looks very ugly for me. Is it well known Androind pattern or just bad design ?
Let's assume that'm sending httpRequest from onCreateView of MyActivity using Volley. Once I received response I need to update Fragment1 UI from callback. How can I do it ?
Should I try to find fragment using findFragmentByTag in my activity and update UI directly ? Is http volley response is in the same thread ? If no, it is ok to update UI from different thread ?
Should I use a Handler class to send message from Activity to Fragment ?
Should I try to find fragment using findFragmentByTag in my activity and update UI directly ? Is http volley response is in the same thread ? If no, it is ok to update UI from different thread ?
findFragmentByTag will not be useful since as soon as replace is
called, the previous fragment is destroyed. If you have only few
fragment you want to switch, you can use hte below solution :
How can I switch between two fragments, without recreating the fragments each time?
Answer to second part of your question 1 :
Volley response is always called on the main thread to its perfectly
ok to update UI on the callback. You should never update UI elements
on any thread other than MAIN / UI thread.
Should I use a Handler class to send message from Activity to Fragment ?
You can choose to send the message by handler but if usually android documentation suggests to send communicate between activity and attached fragments via callbacks
https://developer.android.com/training/basics/fragments/communicating.html
I have followed the official documentation http://developer.android.com/guide/practices/tablets-and-handsets.html for Tablet support to create dual pane layout that works as shown below, in that in small screens (phones) it uses one Fragment inside one Activity to display a list of objects and another fragment inside another Activity.
Every other documentation I read talks about a one way flow from Master to details, now I want to go back the other way, from details to master and I am stuck.
In the details, I have added an Item that I want to display in the list and I want this to be dynamic such that I can add few items and each time I hit save I want the List to grow.
This is what I have done so far
In FragmentA(List Fragment) I have a method that (re)loads the data and call notifyDataSetChanged on the adapter.
I added a method in the call back that is called each time an item is added. And both Activity A and Activity B implements this listener
So when I add an item in FragmentB(Details Fragment) I call the listener and on Activity A which is housing the dual pane layout I try this
public void OnNewCustomerAdded() {
Fragment frag = null;
frag = getSupportFragmentManager().findFragmentByTag("CustomertListFragment");
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.detach(frag);
ft.attach(frag);
ft.commit();
}
Unfortunately that throws a NPE, and also if I call the methods directly in the Fragment to reload data, that throws an NPE. The only thing that works with some side effects is this
public void OnNewClientAdded() {
Intent intent = getIntent();
finish();
startActivity(intent);
}
So how can I safely restart a Fragment inside an Activity without restarting the other Fragment.
Wow, this is quite tricky, never expected it to be this challenging. Well this is how I solve it.
First I removed the second activity and reworked the code to show single pane in handheld devices and dual pane in tablets using just one Activity instead of two. This is not necessary but it helps when you are dealing with one set of lifecycles and listeners.
Then to actually have items added in the DetailsFragment appear immediately in the ListFragment while still having the Details Fragment open. I finished the containing Activity, restarted it and passed it an intent that tells it to start up the DetailsFragment
Remember that the ListFragment is set to start up no matter which device size. So you just need to start the DetailsFragment and since there is already a call back that does that it makes it easy so here is the code
//Callback method for when an item is added, called from the Details
//Fragment
public void OnNewCustomerAdded() {
Intent mIntent = getIntent();
mIntent.putExtra(Constants.SHOULD_START_CUSTOMER_DETAILS, true);
finish();
startActivity(mIntent);
}
Then in the onCreate of the Activity, after the ListFragment has been started, you do this
boolean shouldStartCustomerDetails = getIntent().getBooleanExtra(Constants.SHOULD_START_CLIENT_DETAILS, false);
if (shouldStartClientDetails){
OnCustomerListItemSelected(0);
}
The OnCustomertListItemSelected is the standard mCallback listener that you get if you created a Master/Details Activity in Android Studio, I just modified it to suit my app like so
/**
* Callback method from {#link OnCustomerListItemSelectedListener}
* indicating that the item with the given ID was selected.
*/
#Override
public void OnCustomerListItemSelected(long id) {
if (mTwoPane) {
// In two-pane mode, show the detail view in this activity by
// adding or replacing the detail fragment using a
// fragment transaction.
CustomerDetailsFragment fragment = CustomerDetailsFragment.newInstance(id);
getSupportFragmentManager().beginTransaction()
.replace(R.id.customeractivity_detail_container, fragment)
.commit();
} else {
// In single-pane mode, simply start the detail activity
// for the selected item ID.
CustomerDetailsFragment fragment =
CustomerDetailsFragment.newInstance(getIntent().getLongExtra(Constants.ARG_ITEM_ID, 0));
getSupportFragmentManager().beginTransaction()
.add(R.id.customeractivity_list_container, fragment)
.commit();
}
}
I have this activity where I run an asynctask that gathers a series of data. This data can be represented in several ways, for the sake of simplicity let's say there's two ways. I want the user to be able to switch between these two representations (listview and graphical view).
So I created a FragmentActivity with my ListFragment and my GraphicFragment.
How do you switch between these two so that user can go back and forth? I tried using replace but then the previous fragment gets destroyed which is not of my interest since its reference has the data passed by my Activity. (i.e. I don't want to recreate the fragment each times the user switches between views).
I also tried hiding and showing but I can't get it to work, one of the fragments is not showing.
Also, when I rotate I guess the fragments get destroyed because I get a nullpointerexception when trying to access them.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
pBar = (ProgressBar) findViewById(R.id.analyzingProgress);
// However, if we're being restored from a previous state, then don't
// create the fragment again (please)
if (savedInstanceState == null) {
firstFragment = new DirListFragment();
secondFragment = new GraphViewFragment();
getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, secondFragment).commit();
fragments= new ArrayList<IFragment>();
fragments.add(firstFragment);
fragments.add(secondFragment);
}
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
if(item.getItemId() == R.id.listView){
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
/*transaction.hide(secondFragment);
transaction.show(firstFragment);*/
transaction.replace(R.id.fragment_container, firstFragment);
transaction.addToBackStack(null);
transaction.commit();
}
else if(item.getItemId() == R.id.graph){
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
/*transaction.hide(firstFragment);
transaction.show(secondFragment);*/
transaction.replace(R.id.fragment_container, secondFragment);
transaction.addToBackStack(null);
transaction.commit();
}
return true;
}
The problem comes when I try to access the fragment fields to update their data:
for(IMyFragment frag: fragments){
frag.updateData(data);
}
I get a NullPointerException after rotating the screen.
Also when switching fragmenents, their data is not updated (i.e. I start with list fragment, it's showing the proper data, then switch to graphic, no data is shown, switch back to list, no data again.)
If the fields are null when using setRetainInstance(true);, then you are recreating them (the fragments) in your activity. Make sure you are setting something in onSaveInstanceState() in your activity so in onCreate(), savedInstanceState will not be null, it can even be an empty bundle or useless variable, ex:
#Override
public void onSaveInstanceState(Bundle outstate){
super.onSaveInstanceState(outstate);
outstate.putBoolean("stateChanged", true);
}
Just make sure you call super before adding your arguments.
On another note, I personally have never stored my fragments in an array. I use a FrameLayout as a fragment container in my activities, just like you, and if I need to update something I just call: getSupportFragmentManager().findFragmentById(R.id.fragment_container).setWhatINeed(); This way I always get the visible fragment.
Let me know how the update goes and I'll update this post with any information I can.