In my MainActivity, I have:
#Override
protected void onResume() {
super.onResume();
checkForCrashes();
checkForTutorial();
checkForUpdates();
setStore();
setup();
}
In setup(), I call initializeTabs() in a callback:
protected void setup() {
final Store s = getStore();
setBackground();
if (s == null) {
unauthorizedHandler();
return;
}
final Context mainActivity = this;
fragments = getTabFragments();
StoresController.getStoreDetails(s, mainActivity, new Callback<StoreDetailDecorator>() {
#Override
public void success(StoreDetailDecorator storeDetailDecorator, Response response) {
s.prettyName = storeDetailDecorator.store.pretty_name;
s.save();
Log.v(TAG, s.prettyName);
TextView toolbar_label = (TextView)findViewById(R.id.toolbar_label);
toolbar_label.setText(MainActivity.getTruncatedMenuName(s.name()));
SummaryTab t1 = (SummaryTab)fragments.get(0);
t1.notifier = (SummaryTabLoadingNotifier)mainActivity;
initializeTabs(s, fragments);
t1.populateReport();
}
}
}
public void initializeTabs(Store s, List<Fragment> fragments ) {
ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager(), getTabTitles(), fragments);
ViewPager pager = (ViewPager) findViewById(R.id.pager);
pager.setOffscreenPageLimit(2);
pager.setAdapter(adapter);
}
That last line is crashing for some customers.
java.lang.IllegalStateException: Fragment InfoTab{1bcb80f} is not currently in the FragmentManager
But not every time. Why would this happen sometimes? Something to do with the fragment not being connected? I read some things about checking isAdded() on a fragment, but that's for checking if a fragment is added to an Activity. I'm using a FragmentStatePagerAdapter :
public class ViewPagerAdapter extends FragmentStatePagerAdapter {
private CharSequence[] titles;
private List<Fragment> fragmentList = new ArrayList<>();
public ViewPagerAdapter(FragmentManager fragmentManager, CharSequence titles[], List<Fragment> fragmentList) {
super(fragmentManager);
this.titles = titles;
this.fragmentList = fragmentList;
}
#Override
public android.support.v4.app.Fragment getItem(int position) {
return fragmentList.get(position);
}
#Override
public CharSequence getPageTitle(int position) { return titles[position]; }
#Override
public int getCount() { return fragmentList.size(); }
#Override
public Parcelable saveState() {
return null;
}
}
Could saveState(){ return null;} be it? This is there, as I understand things, to ensure the view is refreshed every time.
onResume() is called every time your activity returns to foreground.
If one of your users, for example, presses the home button and returns to your app then getStoreDetails() will be called again and you'll create a new adapter (with the same old fragment list) when the result arrives.
You should instantiate your fragments, create an adapter and populate your viewpager with the adapter only once (onCreate() would be the spot).
When new data arrives from getStoreDetails() just update the already added fragments with the new data.
This is because you are returning null in saveState(). You shouldn't try to override this behavior.
But even if you want to do it for some reason, you have to override public void restoreState(Parcelable state, ClassLoader loader) as well to avoid crash, because that method is relying on data saved in saveState().
But I would suggest you to not override it at all, and instead explain what you wanted to achieve there.
Related
This question already has answers here:
Passing ArrayList from Fragment class to Activity
(2 answers)
Closed 5 years ago.
please give me good solution I need your help!.
I want to pass parameter using Interface class
from B Fragment to Activity and from Activity to C Fragment.
But Service asynctask I don't know C waiting for B ?
Please explain me this Fragments do what ?
EDIT:
public class FeedDetailActivity extends AppCompatActivity implements TabFragment_DetailFeed.ArrayPasser {
private Toolbar toolbar;
private TabLayout tabLayout;
private ViewPager viewPager;
String nameSurname;
int feedId;
public ArrayList<String> pathimg;
Bundle bundle;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_feeddetail);
// toolbar = (Toolbar) findViewById(R.id.toolbar);
// setSupportActionBar(toolbar);
// getSupportActionBar().setDisplayHomeAsUpEnabled(true);
nameSurname = getIntent().getStringExtra("nameSurname");
Intent i = getIntent();
feedId = i.getIntExtra("feedId",0); // 20 for default value
// feedId = getIntent().getStringExtra("feedId"); // Hata burda
viewPager = (ViewPager) findViewById(R.id.pager);
setupViewPager(viewPager);
tabLayout = (TabLayout) findViewById(R.id.tab_layout);
tabLayout.setupWithViewPager(viewPager);
/* TabFragment_DetailComment EditTextinden otomatik klavye açtırmasını kapatmasını sağlıyor. */
getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN
);
}
public String getNameSurname() {
return nameSurname;
}
public int getFeedId() {
return feedId;
}
private void setupViewPager(ViewPager viewPager) {
FeedDetailActivity.ViewPagerAdapter adapter = new FeedDetailActivity.ViewPagerAdapter(getSupportFragmentManager());
adapter.addFragment(new TabFragment_DetailFeed(), "DETAIL");
adapter.addFragment(new TabFragment_DetailComment(), "COMMENT");
adapter.addFragment(new TabFragment_DetailImage(), "IMAGE");
adapter.addFragment(new TabFragment_DetailSurvey(), "SURVEY");
viewPager.setAdapter(adapter);
}
#Override
public void sendArray(ArrayList<String> strings) {
TabFragment_DetailImage frag = (TabFragment_DetailImage)
getSupportFragmentManager().findFragmentById();
frag.method(strings);
}
class ViewPagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> mFragmentList = new ArrayList<>();
private final List<String> mFragmentTitleList = new ArrayList<>();
public ViewPagerAdapter(FragmentManager manager) {
super(manager);
}
#Override
public Fragment getItem(int position) {
return mFragmentList.get(position);
}
#Override
public int getCount() {
return mFragmentList.size();
}
public void addFragment(Fragment fragment, String title) {
mFragmentList.add(fragment);
mFragmentTitleList.add(title);
}
#Override
public CharSequence getPageTitle(int position) {
return mFragmentTitleList.get(position);
}
}
}
You should try to use EventBus. With it you can register receivers in your fragments and post events from any part of code.
Here is a little example for your fragments:
In C fragment override onResume() and onPause() like this
#Override
public void onResume() {
super.onResume();
EventBus.getDefault().register(this);
}
#Override
public void onPause() {
super.onPause();
EventBus.getDefault().unregister(this);
}
And also subscribe to your event (you need to create class for it)
#Subscribe(threadMode = ThreadMode.MAIN)
public void onResult(OnResultEvent event) {
//do stuff in event
}
Then in B fragment do like this
public void doStuff(){
//do stuff here
EventBus.getDefault().post(new OnResultEvent(result));
}
You Activity implements your interface(ArrayPasser) in this case which is defined in FragmentA
public class YourActivity implements FragmentA.ArrayPasser{
#Override
public void sendArray(ArrayList<String> strings){
// Get instance of Fragment B using FragmentManager
FraB frag = (FragB)
getSupportFragmentManager().findFragmentById(R.id.fragment_b);
frag.someMethod(strings); //passing arraylist to Fragment B from
Activity
}
}
// Fragment A defines an Interface, and calls the method when needed
public class FragA extends Fragment{
ArrayPasser mCallback; //interface reference
public interface ArrayPasser{
public void sendArray(ArrayList<String> strings);
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mCallback = (ArrayPasser) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement ArrayPasser");
}
}
public void someMethod(ArrayList<String>strings){
mCallback.sendArray(strings); //passing array list to fragment
}
#Override
public void onDetach() {
mCallback = null; // => avoid leaking
super.onDetach();
}
}
// Fragment B has a public method to do something with the Array
public class FragB extends Fragment{
public void method(ArrayList<String> strings){
// Here you have it
}
}
I have problem that it takes long time to load data and show only when i scroll the view pager. Several time i already used asynctask but not work. After changing tab, i called same fragment any one help me??
Here, is first time loaded data
After changing tab call same fragment
Here is my Adapter for ViewPager
public class AdapterCategoryViewPager extends FragmentPagerAdapter {
Context context;
private List<Fragment> fragmentList;
private List<Type> typeList;
public AdapterCategoryViewPager(FragmentManager fm, Context context, List<Fragment> fragmentList, List<Type> typeList) {
super(fm);
this.context = context;
this.fragmentList = fragmentList;
this.typeList = typeList;
}
#Override
public Fragment getItem(int position) {
return fragmentList.get(position);
}
#Override
public CharSequence getPageTitle(int position) {
return typeList.get(position).getType().toString();
}
#Override
public int getCount() {
return fragmentList.size();
}
}
Here is my Fragment for category this function i call on async task
private void setData(View v)
{
addWomenCategory();
ll_list.removeView(v);
gridView.removeView(v);
gridView.setMode(PullToRefreshBase.Mode.PULL_FROM_END);
adapterCategory=new AdapterCategory(v.getContext(),categoryArrayList,height,width);
gridView.setAdapter(adapterCategory);
g=(GridView) gridView.getRefreshableView();
g.setHorizontalSpacing(h7);
g.setVerticalSpacing(h7);
// g.setEnabled(false);
gridView.setOnRefreshListener(new PullToRefreshBase.OnRefreshListener<GridView>() {
#Override
public void onRefresh(final PullToRefreshBase<GridView> refreshView) {
gridView.postDelayed(new Runnable() {
#Override
public void run() {
refreshView.getLoadingLayoutProxy().setRefreshingLabel("Loding Data...");
refreshView.getLoadingLayoutProxy().setReleaseLabel("Refresh complete");
addWomenCategory();
adapterCategory.notifyDataSetChanged();
gridView.setRefreshing(false);
gridView.onRefreshComplete();
}
},2000);
}
});
}
Here is my HomeFragment which actually add fragments category in array list this function i also called in asynctask
private void setData(View v)
{
addType();
pager.setOffscreenPageLimit(3);
adapterType=new AdapterType(typeArrayList,v.getContext(),height,width);
fragmentList.add(new FragmentListCategory());
fragmentList.add(new FragmentMenListCategory());
fragmentList.add(new FragmentKidListCategory());
fragmentList.add(new FragmentListCategory());
fragmentList.add(new FragmentMenListCategory());
fragmentList.add(new FragmentKidListCategory());
pager.setAdapter(new AdapterCategoryViewPager(getFragmentManager(),v.getContext(),fragmentList,typeArrayList));
tabs.setViewPager(pager);
setUpTabStrip(v);
}
I have used universal imageloader library to display image and i was initialized its object every time in adapter to solve this issue i initialize that configuration object only once in whole application in main activity's onCreate() method
As addWomenCategeory() being an asynchronous task, the code in the main thread doesn't wait for the result.
Consider moving the code
adapterCategory.notifyDataSetChanged(); gridView.setRefreshing(false); gridView.onRefreshComplete();
to addWomencategory() method, where you recieve result from your back-end
Main goal is to update Fragment info mainly from its own class.
Main activity:
public class MainActivity extends AppCompatActivity {
final Handler GUIHandler = new Handler();
final Runnable r = new Runnable()
{
public void run()
{
updateFragments();
GUIHandler.postDelayed(this, 1000);
}
};
#Override
protected void onPause() {
super.onPause();
GUIHandler.removeCallbacks(r);
}
#Override
protected void onResume() {
super.onResume();
GUIHandler.postDelayed(r, 600);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
...
mViewPager = (ViewPager) findViewById(R.id.pager);
mPagerAdapter = new PagerAdapter(getSupportFragmentManager(), tabLayout.getTabCount());
mViewPager.setAdapter(mPagerAdapter);
...
}
private void updateFragments() {
mPagerAdapter.updateFragments();
}
PagerAdapter:
public class PagerAdapter extends FragmentStatePagerAdapter {
int mNumOfTabs;
private Observable mObservers = new FragmentObserver();
public PagerAdapter(FragmentManager fm, int NumOfTabs) {
super(fm);
this.mNumOfTabs = NumOfTabs;
}
#Override
public Fragment getItem(int position) {
mObservers.deleteObservers(); // Clear existing observers.
switch (position) {
case 0:
FragmentWeather weatherTab = new FragmentWeather();
weatherTab.setActivity(mActivity);
if(weatherTab instanceof Observer)
mObservers.addObserver((Observer) weatherTab);
return weatherTab;
case 1:
FragmentMemo tab2 = new FragmentMemo();
return tab2;
case 2:
FragmentHardware tab3 = new FragmentHardware();
return tab3;
default:
return null;
}
}
public void updateFragments() {
mObservers.notifyObservers();
}
}
FragmentObserver
public class FragmentObserver extends Observable {
#Override
public void notifyObservers() {
setChanged(); // Set the changed flag to true, otherwise observers won't be notified.
super.notifyObservers();
Log.d("Observer", "Sending notification");
}
}
FragmentWeather:
public class FragmentWeather extends Fragment implements Observer {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
...
return layout;
}
public void setTemperatures(){
Log.d("Android", "setTemperatures is called");
}
#Override
public void update(Observable observable, Object data) {
setTemperatures();
}
}
Problem now is, that PagerAdapter::getItem() doesnt get called when Fragments are created at the start of application. That means WeatherFragment dont get associated with mObservers. If I swipe to the 3rd view and then swipe back, everything is working properly. How to restructurize this to make it working?
this line:
mObservers.deleteObservers(); // Clear existing observers.
is removing all the observers, but the method getItem gets called several times, that means only the last time it calls anything stays there. REMOVE this line.
Also, the following code is a very bad pattern and it will go wrong on several occasions:
case 0:
FragmentWeather weatherTab = new FragmentWeather();
weatherTab.setActivity(mActivity);
if(weatherTab instanceof Observer)
mObservers.addObserver((Observer) weatherTab);
return weatherTab;
that's because fragments get re-created by the system when necessary, so setActivity is pointless, so as is addObserver. The moment the system needs to destroy/recreate the fragments, you'll have a memory leak of those old fragments, the old activity, and the new ones won't have the activity and won't be on the observers.
The best situation here is to rely on the natural callbacks from the fragments. An example follows (ps.: that was typed by heart, I'm sure there might be some mistakes, but you'll get the idea)
public interface ObservableGetter{
public Observable getObservable();
}
public void MyFragment extends Fragment implements Observer {
#Override onAttach(Activity activity){
super.onAtttach(activity);
if(activity instanceof ObservableGetter){
((ObservableGetter)activity).getObservable().
addObserver(this);
}
}
#Overrude onDetach(){
Activity activity = getActivity();
if(activity instanceof ObservableGetter){
((ObservableGetter)activity).getObservable().
removeObserver(this);
}
super.onDetach();
}
}
then you can just make the activity implements ObservableGetter and have the Observable on it.
Then your adapter code will be just:
case 0:
return new FragmentWeather();
all the rest of the logic uses the regular callbacks.
I hope it helps.
I'm having a strange issue with fragment views not refreshing properly, but it only happens after the app is closed and re-opened after some amount of time.
I have a TabSwitcherActivity which contains a ViewPager. The ViewPager switches between 3 fragments, and each fragment represents a different view of the same information. There are certain events that can happen which will cause the information to become stale, so the fragments need to be notified to refresh the view. To accomplish this, my TabSwitcherActivity has a method called notifyDataSetChanged() which will iterate the 3 fragments and tell them to refresh. Here is (I think) the most relevant code:
public class TabSwitcherActivity extends FragmentActivity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Used to keep track of the child fragments of this activity so we can update their list adapters.
mFragments = new ArrayList<Fragment>();
// Set up the view pager and tab adapter.
setContentView(R.layout.pager_layout);
mViewPager = (ViewPager) super.findViewById(R.id.pager);
mFragments.add(Fragment.instantiate(this, FragmentOne.class.getName()));
mFragments.add(Fragment.instantiate(this, FragmentTwo.class.getName()));
mFragments.add(Fragment.instantiate(this, FragmentThree.class.getName()));
mViewPager.setAdapter(new ListPagerAdapter(super.getSupportFragmentManager(), mFragments));
}
public void notifyDataSetChanged() {
for (Fragment fragment : mFragments) {
if (fragment instanceof IDataSetChangedListener) {
((IDataSetChangedListener) fragment).notifyDataSetChanged();
}
}
}
}
Now, this works as intended the first time the app is launched. The problem is that after the app is closed and re-opened some time later, calling TabSwitcherActivity.notifyDataSetChanged() while the app is running does not update the fragment views (this works the first time the app is started after a reboot). This leads me to believe that there is a life cycle situation with the fragments that I'm not handling correctly. Any idea what this might be? It occurs to me that If the fragments are destroyed and recreated, they are probably not correctly stored in my mFragments array.
I'm answering my own question in case others have a similar problem to what I had. I was able to resolve the problem based on the comments from invertigo. I'll post some generified pieces of the important parts of the code.
As I suspected, the fragments stored in the array were not correctly updated if they were destroyed and re-created by the application. (This would sometimes happen when the application was closed and re-opened.) It was a little bit tricky to remove the array since I was using it to store the fragments for my tabs in my FragmentPagerAdapter implementation.
Here is my old ListPagerAdapter class:
public class ListPagerAdapter extends FragmentPagerAdapter {
private List<Fragment> mFragments;
public ListPagerAdapter(FragmentManager fm, List<Fragment> fragments) {
super(fm);
mFragments = fragments;
}
#Override
public Fragment getItem(int position) {
return mFragments.get(position);
}
#Override
public int getCount() {
return mFragments.size();
}
#Override
public String getPageTitle(int position) {
return ((ITitledFragment) mFragments.get(position)).getTitle();
}
}
Here is my new implementation, which removes the problem caused by the array holding bad fragment references:
public class ListPagerAdapter extends FragmentPagerAdapter {
private static final int FRAGMENT_ONE_POSITION = 0;
private static final int FRAGMENT_TWO_POSITION = 1;
private static final int FRAGMENT_THREE_POSITION = 2;
private static final int COUNT = 3;
public ListPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
switch (position) {
case FRAGMENT_ONE_POSITION:
return new FragmentOne();
case FRAGMENT_TWO_POSITION:
return new FragmentTwo();
case FRAGMENT_THREE_POSITION:
return new FragmentThree();
}
return null;
}
#Override
public int getCount() {
return COUNT;
}
#Override
public String getPageTitle(int position) {
switch (position) {
case 0:
return FragmentOne.getTitle();
case 1:
return FragmentTwo.getTitle();
case 2:
return FragmentThree.getTitle();
}
return null;
}
}
Then, the only remaining problem is how to have the my TabSwitcherActivity class update all of the fragments when they are stale. As invertigo pointed out, this can be done with tags. However, I decided to use an approach where each fragment registers itself with the activity when it is created, and unregisters itself before it is destroyed. The activity can iterate registered fragments to update them.
The Relevant code from TabSwitcherActivity:
class TabSwitcherActivity extends FragmentActivity {
private List<Fragment> mFragments;
private void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Used to keep track of the child fragments of this activity so we can update their list adapters.
mFragments = new ArrayList<Fragment>();
// Set up the view pager and tab adapter.
setContentView(R.layout.pager_layout);
mViewPager = (ViewPager) super.findViewById(R.id.pager);
mViewPager.setAdapter(new ListPagerAdapter(super.getSupportFragmentManager()));
}
public void startTrackingFragment(Fragment f) {
mFragments.add(f);
}
public void stopTrackingFragment(Fragment f) {
mFragments.remove(f);
}
public void notifyDataSetChanged() {
for (Fragment fragment : mFragments) {
if (fragment instanceof IDataSetChangedListener) {
((IDataSetChangedListener) fragment).notifyDataSetChanged();
}
}
}
}
With that, all that remains is to have the fragments register themselves for updates:
public class FragmentOne extends Fragment implements IDataSetChangedListener {
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
((TabSwitcherActivity) getActivity()).startTrackingFragment(this);
}
public void onDestroy() {
((TabSwitcherActivity) getActivity()).stopTrackingFragment(this);
super.onDestroy();
}
}
You could always just refresh the data in onResume() of your fragments, or call notifyDataSetChanged() in onResume() of your activity.
re: "It occurs to me that If the fragments are destroyed and recreated, they are probably not correctly stored in my mFragments array."
Instead of storing the fragments in an array, use the fragment manager to fetch the fragments by tag or id.
http://developer.android.com/guide/components/fragments.html#Managing
http://developer.android.com/reference/android/app/FragmentManager.html#findFragmentByTag(java.lang.String)
I'm currently trying to work with fragment, but I'm stuck with an issue I can't solve.
I have one activity, which holds 4 different fragment. From this activity, I launch an ASyncTask which goes to the web and get different data I need, and then will send it to the fragments.
But, when my app gets killed and opened again, or when I change the orientation, my fragments are apparently recreated and my custom FragmentAdapter doesn't hold the good reference to the fragment.
Here is the code of my main activity.
public class MainActivity extends FragmentActivity {
MyPagerAdapter fgsAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//...
FragmentManager fm = super.getSupportFragmentManager();
fgsAdapter = new MyPagerAdapter(fm,this);
ViewPager myPager = (ViewPager) findViewById(R.id.home_pannels_pager);
myPager.setAdapter(fgsAdapter);
myPager.setCurrentItem(0);
}
#Override
protected void onResume() {
super.onResume();
ATaskGetUser task = new ATaskGetUser(callback, (ProgressBar) findViewById(R.id.PB_AsyncTask));
task.execute();
}
//What's called by the ASyncTask onPostExecute()
private void notifyDataChanged() {
fgsAdapter.notifyFragments(user.getItems());
}
private class MyPagerAdapter extends FragmentPagerAdapter {
private List<CardFragment> fragments = new ArrayList<CardFragment>();
private Context c;
public MyPagerAdapter(FragmentManager fm, Context c) {
super(fm);
CardFragment h = new HabitFragment();
CardFragment d = new DailyFragment();
CardFragment t = new ToDoFragment();
CardFragment r = new RewardFragment();
fragments.add(h);
fragments.add(d);
fragments.add(t);
fragments.add(r);
}
public int getCount() {
return fragments.size();
}
#Override
public CardFragment getItem(int position) {
Log.v("MainActivity_fgsmanager", "getItem()");
CardFragment f = (CardFragment) this.fragments.get(position);
return f;
}
public void notifyFragments(List<HabitItem> items) {
for(OnTasksChanged f : fragments) {
f.onChange(items);
}
}
}
}
So, what I want to be able to do, is to be able to call the onChange (an interface implemented by my four fragments), in my notifyDataChanged function. Is this possible, are am I thinking the wrong way?
I got the same problems once with Fragments, I was losing the current fragment after every screen rotation.
I simply solved it by adding one line in the Fragment class (not in the parent FragmentActivity class):
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.rate_fragment, container,false);
setRetainInstance(true);//Added not to lose the fragment instance during screen rotations
(...)
For your case where your app gets killed and opened again, I am not sure it will work though.