I have the following design:
MainActivity
| /->AddingActivity
|-------Fragment One---\->DialogFragment
|-------Fragment Two
|-------Fragment Three
|-------Fragment Four
MainActivity hold the fragments. Fragment One can start AddingActivity (with context from MainActivity) which adding data to DB and then all fragments should refresh with the new data. DialogFrgment started from FragmentOne is adding some data to SharedPreference and only the FragmentOne should know about this. MainActivity impliments DialogFragment.OnTimeSetListener (The dialog picks time) and then in MainActivity onTimeSet triggers function in Fragment One to refresh the data.
What I did for now is overridden OnCreate in each fragment and the fragment refreshes when it returns back to view. But the app is bit laggy because each fragment refreshes its neighbors and I get some fragment refreshed twice.
Can you please advise me about the correct approach to this design, and how can I make the app work more smooth and correct.
Your approach of MainActivity implementing DialogFragment.OnTimeSetListener and then notifying FragmentOne seems kind of strange.
This is how I would do it:
FragmentOne and MainActivity implement DialogFragment.OnTimeSetListener as well. However, only FragmentOne should be notified from the dialog fragment when the time changes, as it is the one responsible for setting the time. Then when this happens, FragmentOne should notify the activity:
#Override
public void onTimeSet(Time time) {
((DialogFragment.OnTimeSetListener) getActivity().onTimeSet(time);
}
When MainActivity receives the time set event, it must update the other fragments, but if possible, it should not re-create them. Just let them know the time was set so that they can update their views accordingly. Each fragment will update itself depending on what it is displaying, etc.
In case the problem persists and you still have lag, check if you do too much work on the main thread, e.g. a long-running for-loop and try to execute it on another thread.
Maybe you can use LocalBroadcastManager to refresh the the fragments , that way you can have the control to which fragment to refresh and not all at the same time , and you should also check if the DB is causing the lag.
Here is an example how to use LocalBroadcastManager you should implement it on your own way.
https://android--code.blogspot.com/2015/12/android-how-to-send-and-receive-local.html
Related
I've got a fully working activity. However, I want to adapt the activity to a Fragment so I'm able to transition between the initial activity and any future activities.
However, how do I update the UI after I've called the onCreateView();? I've got a task that is repeated constantly by a handler, so the interface needs to be updated every 0.5 seconds. This doesn't seem possible with a fragment as it seems to be static (it's exited after creation).
So how do I edit/update a fragment's interface after it's creation?
Do I do my activity's work inside the FragmentActivity or the Fragment itself?
1) in your Fragment, init Views in the onCreateView() method, use class fields
2) create public methods inside your Fragment and you can call them from Activity
yourFragInstance.yourMethod();
I have a Fragment that shows some data saved in android Preference.
when these data changes due to various operations of the app, I want to update the fragment with the new data.
I have tried to use
myFragmentTransaction.notifyAll();
But unfortunately doesn't work and the fragment is updated only when I reopen my whole Activity
Houw could I refresh the Fragment on data changes?
notifyAll pertains to threading. Completely unrelated to what you are doing. Instead of trying to tell each fragment to redraw themselves have you tried OnSharedPreferenceChangeListener. Each fragment should listen to changes in the information they care for.
I've an activity with a FragmentStatePagerAdapter that contains two fragments of the same class (practically an activity with two tabs).
At runtime, I need to update both fragments layout with fresh data, after user input.
In particular a user click on a fragment, and the other one is updated consequently.
Any simple method to do that?
Fearing angry backlash because I'm doing ugly things and am now telling about them:
Let your Fragment register itself in the Activity so when you need to reload you can just call the fragment reload function directly.
Probably less ugly:
Let the activity broadcast reload, and have your fragments register a broadcast receiver so they'll know when to reload.
I have a scenario and I an not sure on what path to go.
Scenario
The app has a Home activity which displays various fragments. The data in the fragments can come either from the web or a local database and is retrieved using an asynctask.
From what I saw, I have 2 alternatives:
Put the Asynctask in parent activity and then use fragment.newInstance(parameters) to pass the result to the fragment. However, if in my asynctask I need to update the progress or some info on the fragment, each time I will have to call newInstance with the new set of parameters.
Add the fragment and put the asynctask in it, in this way when progress is needed, I can update the fragment's views, as I have access to them + when the asynctask is done, I can populate the list with the info.
What would be the correct approach ?
LE: actually for point 1 in order to update the fragment I can call fragment's public methods after I find it with findFragmentById in the parent activity
A better way if you have multiple tasks would be to use an IntentService :
http://mobile.tutsplus.com/tutorials/android/android-fundamentals-intentservice-basics/
You would have a better control to what you're requesting and what you want to cancel.
I would go with the second approach.
My primary reason though would be to avoid the issues that can happen on screen orientation change while the AsyncTask is working.
I would go with method 2, but take it a step further.
Have a separate fragment to run your async task. This way, you can handle any configuration changes (not just rotating screen) without any issues.
In another fragment, you can display the data. You can pass the data from your async task fragment via callbacks to the activity, and have the activity call a method in the display fragment to update the data.
My setup is as follows.
I have a FragmentPagerAdapter called from my Activity which loads two fragments. This is setup within onCreate.
In onResume I call an ASyncTask which loads data from a database, and then calls a callback in my activity onLoadComplete via a load data listener.
#Override
public void onLoadComplete(JSONArray data) {
// TODO Auto-generated method stub
LocalFragment fragmentB = (LocalFragment)getSupportFragmentManager().findFragmentByTag(ListTag);
fragmentB.setList(data);
LMapFragment fragmentA = (LMapFragment)getSupportFragmentManager().findFragmentByTag(MapTag);
GoogleMap our_map = fragmentA.getMap();
fragmentA.plotP(myLocation,data);
}
The fragments are initialized by the Pager, and within each fragments code I set the respective tag e.g in LocalFragment
#Override
public void onAttach(Activity activity) {
// TODO Auto-generated method stub
super.onAttach(activity);
String myTag = getTag();
((PagerTest) activity).setListTag(myTag);
Log.d("what",myTag);
}
This allows me to access the fragment, call a function within it which populates a list or populates a map. It works.
What I am now trying to do is account for screen orientation changes.. If while the ASyncTask is running the orientation is changed, the app crashes.
As suggested here: Hidden Fragments I have been trying to implement a hidden fragment which saves the state of my ASyncTask. So what I have done is set it up so in onResume of my Activity i call a function
static LoadDataFromURL the_data = null;
static JSONArray pub_data = null;
private static final String TAG = "RetainFragment";
public RetainFragment() {}
public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {
RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG);
if (fragment == null) {
fragment = new RetainFragment();
fm.beginTransaction().add(fragment, TAG).commit(); // add this
}
return fragment;
}
which essentially saves my data.
Basically what this means is that if i rotate my screen i dont call my ASyncTask again.. the screen just rotates.. it works perfectly.
If however I go back to the main menu and then click on the activity again the screen returns blank (but does not crash). My understanding is that the data is retained as an object in the fragment, but on reloading the activity afresh the data needs to be set again.. I.E onLoadComplete needs to be called to populate the list/map..
So i concluded that if initially after the ASyncTask completes i save the returned data in my hidden fragment onRetainInstance, then i could simply call onLoadComplete and pass it..
The problem is, in this situation seemingly the fragment has not been called yet, as such the tags are null, and calling the callbacks within onLoadComplete crashes the app.
I have been banging my head over this for ages.
My ASyncTask is in a seperate class: LoadDataFromURL
What i want to achieve is as follows - a fragmentviewpager whereby on screen rotate the ASyncTask is retained on rotate/attached to the new activity, and if it has completed before it shouldn't be run again..
Could anyone advise.
Many Thanks
EDIT
Having changed the variables in my secret fragment to public variables, everything has seemingly come together.. BUT because im not 100% sure how/when things are called, I dont fully understand WHY it works..
So.. I call findOrCreateRetainFragment and it either creates a new 'secret' fragment or returns the current instance.
If it is returning a current instance, i dont call my async task again. If it is not, I call my asynctask and load the data.
With this setup, when i load the activity and rotate the screen, it rotates as expected woop.
Now, when i go back to the main menu and then click the activity again, it calls the async task.
My understanding is that on rotate the async task is not called again, and the viewpager is somehow saving the fragments.
On the other hand, when i go back my activity is destroyed, as is my secret fragment, and as such when i click on it again it loads the data. THis is essentially what i want..
Have i understood this correctly?
Thanks
There are a few issues here that you're experiencing (I think).
First of all, the reason your callbacks crash is because they're attached to an old Activity that no longer "exists" after a screen orientation and/or Activity push. If you use onAttach() to attach a callback to your fragment, you must use onDetach() to detach that callback when the Fragment is removed from the Activity. Then, whenever you call the callback, check for a null so you don't send data to a dead object.
Basically, the logic you're trying to use here is:
Start Activity.
Check if your Fragment exists. If it does, grab it. Else, create it.
Retrieve the data if it exists. If not, wait for the callback.
Because of the nature of callbacks (depending on your implementation), you will not receive data until the event fires. However, if the Activity is gone and the event has already fired, the callback won't execute. Thus, you have to retrieve the data manually. When using setRetainInstance(), it's helpful to think of it as this entity detatched from your Activity. It will exist as long as you don't pop the current Activity or push a new Activity. However, your current Activity will be destroyed upon screen orientation changes while the Fragment won't. As such, the Fragment shouldn't rely on the existence of the Activity.
A much more elegant solution to the problem that you may want to look in to is implementing the Android Loader API. Loaders are handy tools that are handled by the system that work is roughly the same way but are more in-tune with asynchronously retrieving data. They work effectively the same way. You simply start your loader and the system with either create one if it doesn't exist or re-use one that already exists. It will remain in the system by the LoaderManager upon configuration changes.
EDIT:
To answer your edit, I guess I'll explain what's happening. It's convoluted, so just tell me if anything needs clarification.
Fragments aren't technically speaking part of your currently running Activity. When you create an instance of the Fragment, you have to call beginTransation() and commit() on the FragmentManager. The FragmentManager is a singleton that exists within the realm of your application. The FragmentManager holds on to the instance of the Fragment for you. The FragmentManager then attaches the Fragment to your Activity (see onAttach()). The Fragment then exists within the FragmentManager which is why you never really have to hold a reference to it within your application. You can just call findFragmentByTag/Id() to retrieve it.
Under normal circumstances, when your Activity is being destroyed, the FragmentManager will detach the instance of your Fragment (see onDetach()) and just let it go. The Java garbage collect will detect that no reference to your Fragment exists and will clean it up.
When you call setRetainInstace(), you're telling the FragmentManager to hold on to it. Thus, when your Activity is being destroyed on a configuration change, the FragmentManager will hold on to the reference of your Fragment. Thus when your Activity is rebuilt, you can call findFragmentByTag/Id() to retrieve the last instance. So long as it didn't keep any context of the last Activity, there shouldn't be any problems.
Traditionally, one would use it to keep references to long standing data (as you are) or to keep connection sockets open so a phone flip doesn't delete it.
Your ViewPager has nothing to do with this. How it retrieves the Fragments is completely dependent on how you implement that Adapter that it's attached to. Usually, retained Fragments don't have Views themselves because Views hold Context data of the Activity they were created in. You would just basically want to make it a data bucket to hold on to the data for the Views to pull from when they're being inflated.