i have a view pager with 3 fragments. i use retrofit rest api to populate each of my fragments for the first time. What i would like to achieve is when the user swipes back either in first or third fragment(those 2 fragment are being being destroyed by the view pager) to restore the data(saved in an array list) and not make a rest api call again. What i have done is to save the array list of downloaded data in onSaveInstanceState() and successfully retrieve it when the user swipes back to one of the 2 above fragments only FOR THE 1 FIRST TIME. The problem is when i navigate back to either one of the 2 fragments the specific bundle key where the array list was saved contains null value.
CompletedSurveysFragment(the third fragment):
public class CompletedSurveysFragment extends Fragment implements SAMVCView {
private static final String debugTag = CompletedSurveysFragment.class.getSimpleName();
private View view;
private RecyclerView completedSurveysRcV;
private SAMVCPresenterImpl SAMVCpresenterImpl;
private SurveysRcvAdapter surveysRcvAdapter;
private List<SurveyData> data;
public CompletedSurveysFragment() {}
public static CompletedSurveysFragment newInstance() {
return new CompletedSurveysFragment();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.e(debugTag, "onCreateView");
if ( view == null ) view = inflater.inflate(R.layout.fragment_completedsurveys, container, false);
completedSurveysRcV = (RecyclerView) view.findViewById(R.id.completedSurveysRcV);
return view;
}
// TODO: 21/6/2016 configure Limit and offset values
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Log.e(debugTag, "onActivityCreated " + savedInstanceState);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity());
completedSurveysRcV.setHasFixedSize(true);
completedSurveysRcV.setLayoutManager(linearLayoutManager);
completedSurveysRcV.addItemDecoration(new DividerItemDecoration(ContextCompat.getDrawable(getActivity(), R.drawable.divider)));
if (savedInstanceState == null) {
SAMVCpresenterImpl = new SAMVCPresenterImpl(this);
SAMVCpresenterImpl.getSurveysBasedOnSpecificFirmId(new AllSurveysBody(getResources().getString(R.string.list_surveys), LoginActivity.getSessionPrefs(getActivity()).getInt(getResources().getString(R.string.firm_id), 0), getResources().getString(R.string.completed), 8, 0));
surveysRcvAdapter = new SurveysRcvAdapter(null, completedSurveysRcV);
completedSurveysRcV.setAdapter(surveysRcvAdapter);
} else {
//Log.e(debugTag, savedInstanceState.+"");
if (savedInstanceState.getParcelableArrayList("data") != null) {
Log.e(debugTag, "here "+ savedInstanceState);
surveysRcvAdapter = new SurveysRcvAdapter(savedInstanceState.<SurveyData>getParcelableArrayList("data"), completedSurveysRcV);
completedSurveysRcV.setAdapter(surveysRcvAdapter);
surveysRcvAdapter.notifyDataSetChanged();
}
//Log.e(debugTag, getArguments().getParcelableArrayList("data")+"");
}
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelableArrayList("data", (ArrayList<? extends Parcelable>) this.data);
Log.e(debugTag, "CompletedFragment onSaveInstanceState "+ outState);
}
#Override
public void onSuccessSurveysFetched(List<SurveyData> data) {
this.data = data;
surveysRcvAdapter = new SurveysRcvAdapter(data, completedSurveysRcV);
completedSurveysRcV.setAdapter(surveysRcvAdapter);
surveysRcvAdapter.notifyDataSetChanged();
}
#Override
public void onFailure() {
}
}
View pager adapter:
public class SurveysPagerAdapter extends FragmentStatePagerAdapter {
private static final String debugTag = SurveysPagerAdapter.class.getSimpleName();
private List<SurveyData> data;
String[] tabText;
public SurveysPagerAdapter(FragmentManager fragmentManager, String[] tabText) {
super(fragmentManager);
this.tabText = tabText;
}
#Override
public Fragment getItem(int position) {
Log.e("SurveysPagerAdapter", position+"");
switch (position) {
case 0:
return CompletedSurveysFragment.newInstance();
case 1:
return OngoingSurveysFragment.newInstance();
case 2:
return PendingSurveysFragment.newInstance();
default:
return null;
}
}
#Override
public CharSequence getPageTitle(int position) {
return tabText[position];
}
#Override
public int getCount() {
return 3;
}
}
Your issue is that you're always returning a new instance of your fragment. Instead of calling CompletedSurveysFragment.newInstance(); (and your other Fragments) every time user swipes, create an array of fragments and retrieve it this way:
...
Fragment [] pages = new Fragment[getCount()];
...
#Override
public Fragment getItem(int position) {
Log.e("SurveysPagerAdapter", position+"");
switch (position) {
case 0:
if(pages[position] == null)
pages[position] = CompletedSurveysFragment.newInstance();
return pages[position];
...
}
Then, you can fetch and cache your data in onCreate() and retrieve it later in onResume() in your respective fragments.
I think onPause() and onResume() won't work. Fragments are tied to their parent activity so since you browse through fragments parent activity is on an onResume() status. What I suggest you to do is to use singleton objects to fetch and store your data. Each fragment can keep a reference to the respective object and have instant access to the data you need. There is plenty of info about singleton pattern and how it works.
The reason this is happening is because onSaveInstanceState only occurs when the parent activity is closed. Since the parent activity of your viewpager is still active when you switch between your fragments, onSaveInstanceState will not be called.
To get the data to save like you are intending, what I would likely do instead is save the data in onPause() and retrieve the data in onResume().
EDIT
According to the docs you need to put the call to super.onSaveInstanceState(savedInstanceState); after you modify the Bundle.
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
// Save the user's current game state
savedInstanceState.putInt(STATE_SCORE, mCurrentScore);
savedInstanceState.putInt(STATE_LEVEL, mCurrentLevel);
// Always call the superclass so it can save the view hierarchy state
super.onSaveInstanceState(savedInstanceState);
}
Related
I have an Activity with three tabs and three Fragments. First Fragment shows the list of song titles and the second tab displays the selected song. The details of the songs is coming from a database.
I am implementing SearchView feature so that whatever search text user enters, only those songs should be displayed in the index.
This is exactly like the way phone book work in our devices.
I'm not able to understand how to refresh the first Fragment when the search query changes.
I'm basically looking for the method that I can call to refresh the first Fragment.
Got my answer here Android refresh a fragment list from its parent activity as suggested by #Prem. Works perfectly fine for me.
Its is achievable by making an interface
MainActivity.java
public class MainActivity extends Activity {
public FragmentRefreshListener getFragmentRefreshListener() {
return fragmentRefreshListener;
}
public void setFragmentRefreshListener(FragmentRefreshListener fragmentRefreshListener) {
this.fragmentRefreshListener = fragmentRefreshListener;
}
private FragmentRefreshListener fragmentRefreshListener;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button b = (Button)findViewById(R.id.btnRefreshFragment);
b.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if(getFragmentRefreshListener()!=null){
getFragmentRefreshListener().onRefresh();
}
}
});
}
public interface FragmentRefreshListener{
void onRefresh();
}}
MyFragment.java
public class MyFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = null; // some view
/// Your Code
((MainActivity)getActivity()).setFragmentRefreshListener(new MainActivity.FragmentRefreshListener() {
#Override
public void onRefresh() {
// Refresh Your Fragment
}
});
return v;
}}
Below is the one way, you can easily do it. You just have to save the fragment instance. That's all.
private class ViewPagerAdapter extends FragmentStatePagerAdapter {
private SparseArray<Fragment> array = new SparseArray<>();
ViewPagerAdapter(FragmentManager manager) {
super(manager);
}
#Override
public Fragment getItem(int position) {
array.setValueAt(position, myFragment);
return myFragment;
}
#Override
public int getCount() {
return 3;
}
public SparseArray<Fragment> getFragmentArray() {
return array;
}
}
Then from your activity, you can get all viewpager fragment easily from adapter instance.
MyFragment fragment = (MyFragment) adapter.getFragmentArray().get(0); // get first fragment and casting it to your origin fragment
Then create a method on that fragment. Call like below:
fragment.myFragmentMethod(String myData){}
Have a nice day!
I have 2 Fragment and I have to send some id to the Fragment. I use this:
public void onItemLongClick(View view, int position) {
FragmentManager fm = getSupportFragmentManager();
actionOption actionOption = new actionOption();
actionOption.show(fm,"fragment_edit_name");
ToDoModule movie = dbList.get(position);
int y= movie.getId();
Bundle args = new Bundle();
args.putInt("exampleInt", y);
actionOption.setArguments(args);
EditOption editOption = new EditOption();
ToDoModule bl = dbList.get(position);
int z= movie.getId();
Bundle zs = new Bundle();
zs.putInt("int", y);
editOption.setArguments(zs);
}
First Fragment is working, but the second is not sent. Cannot send value to EditOption?
How to solve it?
Its very unusual that, you're trying to pass some data to two Fragment at the same time. It would be great if you could write the situation you have there in brief in your question.
Anyway, #PrerakSola came up with a solution for saving the data you want to pass in a SharedPreference and I do think it should work in your case.
You're trying to pass a movie id to actionOption as well as to editOption. You might try to store the id first in a SharedPreference like this.
From your Activity
public void onItemLongClick(View view, int position) {
// ... Your code
// Save the movie id
SharedPreferences pref = getSharedPreferences("MY_APPLICATION", MODE_PRIVATE);
pref.edit().putInt("MOVIE_ID", movie.getId()).commit();
// Do not pass any bundle to the Fragment. Just transact the Fragment here
}
Now from your Fragment's onCreateView fetch the value from preference.
SharedPreferences pref = getActivity().getSharedPreferences("MY_APPLICATION", MODE_PRIVATE);
String movieID = pref.getInt("MOVIE_ID", 0);
Another way you might try to have a public static int variable which might contain the movie id and you can access it from anywhere from your code.
Hope that helps!
Something like this , you can do it
public interface SetData {
public void data(String id);
}
From your activity class or on item click listner
SetData setData;
setData.setDrawerEnabled("anydata");
Infragment , YourFragment extends Fragment implements SetData
hi yesterday i have done same thing and how it work, i'll give you idea.
It already answered but just i want to share my experiance.This way is perfect.
First of all create two interfaces in your activity,
public interface TaskListener1 {
public void onResultAvailable(String result);
}
public interface TaskListener2 {
public void onResultAvailable(String result);
}
Now come to your activity then call like this where you want to send data to fragment.I'm just giving you example.You can make it as you want.
class TestAsyncTask extends AsyncTask<Void, String, Void> {
String response_result;
public TaskListener1 taskListener1 = null;
public TaskListener2 taskListener2 = null;
public TestAsyncTask(TaskListener1 taskListener1, TaskListener2 taskListener2) {
this.taskListener1 = taskListener1;
this.taskListener2 = taskListener2;
}
#Override
protected Void doInBackground(Void... unused) {
response_result = "Test data what you want to send";
return null;
}
#Override
protected void onPostExecute(Void unused) {
taskListener1.onResultAvailable(response_result);
taskListener2.onResultAvailable(response_result);
}
}
Call like this,
new TestAsyncTask(new Fragment1), new Fragment2)).execute();
And how to get data in fragment,
First fragment,
public class Fragment1 extends Fragment implements YourActivity.TaskListener1 {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
view = inflater.inflate(R.layout.fragment1, container, false);
return view;
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
#Override
public void onResultAvailable(String result) {
Logs.d("TAG", "Fragment result1:" + result);
}
}
Second fragment,
public class Fragment2 extends Fragment implements YourActivity.TaskListener2 {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
view = inflater.inflate(R.layout.fragment2, container, false);
return view;
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
#Override
public void onResultAvailable(String result) {
Logs.d("TAG", "Fragment result2:" + result);
}
}
Thanks hope this will help somebody.
I have a viewpager with three pages that uses FragmentStatePagerAdapter.
The fragment for each page is same but the data changes depending on the position of the page. The inflation logic for each item on the page is defined in fragment's OnCreateView, and thats why each time new instance of fragment is inflated the network calls are repeated even though they have been already made on previous visit to that page.
My question is how do I prevent this. I am new to android and I know I am missing something here, IF my approach is wrong please point out about how should I prevent this behavior.
Some Code :
inside activity's oncreate
ViewPager mViewPager = (ViewPager) findViewById(R.id.vpBooks);
PagerAdapter mPagerAdapter = new BooksPageAdapter(getSupportFragmentManager(), MainActivity.this, extras);
mViewPager.setAdapter(mPagerAdapter);
inside viewpageradapter
public BooksPageAdapter(FragmentManager fm, Context context, Bundle extras) {
super(fm);
this.extras = extras;
this.cls = extras.getStringArray("cls");
this.context = context;
}
#Override
public Fragment getItem(int position) {
return BooksPageFrag.newInstance(extras, cls[position]);
}
#Override
public int getCount() {
return cls.length;
}
inside fragment :
#Override
public void onCreateView(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
url = getArguments().getString("url");
urlFare = getArguments().getString("Fare");
Log.d("url sapf", url);
queue = Volley.newRequestQueue(this.getActivity());
getBooks();// HERE network calls are made
}
so what I want is if the fragement for a particular cls[position] is instantiated and data is fetched then on revisiting same position it should not make new network calls
You need to use parcelable . I think you are using a list array of a class object
.Follow the following steps
1.) Implement parcelable in the class object
2.)In the OnSavedInstanceState use the following code
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelableArrayList("key", mListParcel);
}`
3.) In the onCreate method use this code
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
/*Set Your RecyclerView Stuff here*/
if(savedInstanceState!=null)
{
//Fragment has been loaded earlier\
mArrayList=savedInstanceState.getParcelableArrayList("key");
//Use list here
}
else
{
//Fragment New
// Make Request Here
}
}
I wrote an activity with ViewPager, which gets populated after an AsyncTask is executed. Each TestDataObject is tied to the relevant TestFragment. When the screen is rotated the application crushes due to a NullPointerException inside onCreateView method. I believe this is because of ViewPager/Adapter onSaveInstanceState methods, onCreateView tries to restore data prior to the AsyncTask data load when data isn't available yet.
I could just if onCreateView code but it doesn't feel to me like a right solution, because amount of fragments inside ViewPager might vary so it might end up doing unnecessary job: restore altered viewpager content and then replace with initial. In this case onSaveInstanceState seems to be excessively harmful. Presumably, I could extend ViewPager or Adapter to cancel save procedure - I find it weird as well.
Do you have any better suggestions to offer?
public class MainActivity extends LoggerActivity {
private ArrayList<TestDataObject> mDataObjects = new ArrayList<MainActivity.TestDataObject>();
private ViewPager mViewPager;
private TestFragmentAdapter mViewPagerAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPagerAdapter = new TestFragmentAdapter(
getSupportFragmentManager(), mDataObjects);
mViewPager.setAdapter(mViewPagerAdapter);
new TestAsyncTask().execute();
}
private class TestAsyncTask extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... params) {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
mDataObjects.add(new TestDataObject());
mDataObjects.add(new TestDataObject());
mDataObjects.add(new TestDataObject());
mViewPagerAdapter.notifyDataSetChanged();
}
}
public static class TestFragment extends Fragment {
private TestDataObject mDataObject;
public static TestFragment getInstance(TestDataObject obj) {
TestFragment f = new TestFragment();
f.mDataObject = obj;
return f;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// layout.find...
mDataObject.toString();
return inflater.inflate(R.layout.fragment_test, null, false);
}
}
public static class TestFragmentAdapter extends FragmentStatePagerAdapter {
private List<TestDataObject> mDataObjects;
public TestFragmentAdapter(FragmentManager fm, List<TestDataObject> objs) {
super(fm);
mDataObjects = objs;
}
#Override
public Fragment getItem(int position) {
return TestFragment.getInstance(mDataObjects.get(position));
}
#Override
public int getCount() {
return mDataObjects == null ? 0 : mDataObjects.size();
}
}
public static class TestDataObject {
}
}
I believe this is because of ViewPager/Adapter onSaveInstanceState
methods. onCreateView tries to restore data prior to the asynctask
dataload when data isn't available yet.
That is not what is happening(I'm assuming you get the exception at mDataObject.toString();), even if the AsyncTask would finish its job instantaneously the exception will still be thrown. After the first run of the app the ViewPager will have three fragments in it. When you'll turn the phone the Activity will be destroyed an recreated again. The ViewPager will try to recreate the fragments in it, but this time it will do it by using the default empty constructor(that is why you shouldn't use a non empty constructor to pass data). As you can see, the first time the Fragment is created by the adapter it will be created by the getInstance method(that is also the only point where you initialize mDataObject) to which you pass a TestDataObject object. When the ViewPager reinitializes its fragments that field will not be initialized as well.
If TestDataObject can be put in a Bundle then you could simply adapt your getInstance method to pass some arguments to your fragments(so the data field will be initialized when the ViewPager will recreate them). I'm sure you've seen:
public static TestFragment getInstance(TestDataObject obj) {
TestFragment f = new TestFragment();
// f.mDataObject = obj; <- don't do this
// if possible
Bundle args = new Bundle();
args.put("data", obj); // only if obj can be put in a Bundle
f.setArguments(args);
return f;
}
private TestDataObject mDataObject;
#Override
public void onCreate(Bundle savedInstance) {
mDataObject = getArguments().get("data"); // again, depends on your TestDataObject
}
Another approach would be to pass the smallest amount of data to the Fragment(like above) so it has enough information to recreate it's data whenever it's recreated.
I have a FragmentPagerAdapter that pages through views generated by a custom SurveyPage class. The final page in the pager is generated by SurveyFinishPage, which tries to access the Survey associated with the FragmentPagerAdapter and pulls all of the survey answers together to display them. The Survey object is grabbed by SurveyFinishPage via a getSurvey() method in the main Activity.
Normally this works fine and the SurveyFinishPage is able to access the same Survey object that the FragmentPagerAdapter is using. If the Activity has been killed and restored, though, the SurveyFinishPage seems to still be accessing the old Survey (or maybe a copy of it) - it displays the answers from before the Activity was discarded regardless of changes to answers to the new, rebuilt Survey.
It seems like restoring the value of aSurvey in onRestoreInstanceState() in my MainActivity should establish the survey object for everything else, since MainActivity passes its Survey object to the FragmentPagerAdapter, which uses that object to make Fragments... but instead it seems that the FragmentPagerAdapter and the SurveyFinishPage are looking at two different things.
How can I restore the state of the fragments in my FragmentPagerAdapter properly so that they all reference the same Survey object?
MainActivity.java
public class MainActivity extends FragmentActivity {
SurveyPagerAdapter mSurveyPagerAdapter;
ViewPager mViewPager;
Survey aSurvey;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_poll);
if(savedInstanceState != null) {
testSurvey = savedInstanceState.getParcelable("survey");
}
else {
testSurvey = new Survey();
}
mSurveyPagerAdapter = new SurveyPagerAdapter(getSupportFragmentManager(), aSurvey);
mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPager.setAdapter(mSurveyPagerAdapter);
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable("survey", aSurvey);
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
aSurvey = (Survey)savedInstanceState.getParcelable("survey");
}
[...]
}
SurveyPagerAdapter.java
public class SurveyPagerAdapter extends FragmentPagerAdapter {
private Survey survey;
[...]
#Override
public Fragment getItem(int i) {
Fragment fragment;
Bundle args = new Bundle();
// index 0 is the title page
if(i == 0) {
fragment = new SurveyTitlePage();
}
// final index is the finish page
else if(i == getCount()-1) {
fragment = new SurveyFinishPage();
}
// all other indices are regular poll pages
else {
args.putParcelable("question", survey.getQuestion(i-1));
fragment = new SurveyPage();
fragment.setArguments(args);
}
return fragment;
}
[...]
}
SurveyFinishPage.java
public class SurveyFinishPage extends Fragment {
private Survey survey;
[...]
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
setSurvey(((MainPoll)getActivity()).getSurvey());
LinearLayout view = new LinearLayout(getActivity());
view.setOrientation(LinearLayout.VERTICAL);
Button finishButton = new Button(getActivity());
finishButton.setText("FINISH");
final TextView textView = new TextView(getActivity());
finishButton.setOnClickListener(new Button.OnClickListener() {
#Override
public void onClick(View v) {
String output = "";
for(int i=0; i<survey.getSize(); i++) {
try {
output += survey.getQuestion(i).getPrompt() + " - " + survey.getQuestion(i).getAnswer() + "\n";
} catch (Exception e) {
output += survey.getQuestion(i).getPrompt() + " - " + "no answer\n";
}
}
textView.setText(output);
}
});
view.addView(finishButton);
view.addView(textView);
return view;
}
}
SurveyPage.java
public class SurveyPage extends Fragment {
[...]
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Bundle args = getArguments();
Question question = args.getParcelable("question");
View pageView = question.getQuestionView(getActivity(), container);
return pageView;
}
}
You could lift the Survey object to the Android application class and reference it as a singleton. That answer will likely create some debate, but it is a reasonable solution to your problem. Android guarantees there will only ever be 1 instance of your application so you can be certain (within reason) that you'll have only 1 instance of Survey.
http://developer.android.com/guide/faq/framework.html
Singleton class
You can take advantage of the fact that your application components run in the same process through the use of a singleton. This is a class that is designed to have only one instance. It has a static method with a name such as getInstance() that returns the instance; the first time this method is called, it creates the global instance. Because all callers get the same instance, they can use this as a point of interaction. For example activity A may retrieve the instance and call setValue(3); later activity B may retrieve the instance and call getValue() to retrieve the last set value.