Android : FragmentPagerAdapter don't saved the state of Fragment? - java

I have a problem with the FragmentPagerAdapter .
I can not save the state of the Fragment and then there is the view that within the Fragment . Whenever I use the swipe left and right , the Fragment is recreated by overriding the method getItem ( int position ) in the static class that extends the FragmentPagerAdapter .
public static class GraphicsCollectionPagerAdapter extends FragmentPagerAdapter {
final int NUM_ITEMS = 3; // number of tabs
public GraphicsCollectionPagerAdapter(FragmentManager fm) {
super(fm);
fragmentList = new AnalyzeFragmentPageListWithDate();
fragment1 = new AnalyzeFragmentPage1();
fragment2 = new AnalyzeFragmentPage2();
}
#Override
public Fragment getItem(int position) {
//Log.i(TAG, "getItem() -> New fragment at position " + position);
switch (position) {
case 0:
return fragmentList;
case 1:
return fragment1;
case 2:
return fragment2;
}
return null;
}
#Override
public int getCount() {
return 3;
}
#Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return "Fragm1";
case 1:
return "Fragm2";
case 2:
return "Fragm3";
}
return "OBJECT " + (position + 1);
}
}
Within the method OnCreateView of each instance of the Fragment there are several steps to the SQLite database and this causes a saturation of the Database Connection Pool.
The warning found whenever change dynamically fragment is: "W / SQLiteConnectionPool (1111 ) : A SQLiteConnection object for database ' / data / data / com.myapp / databases / mydb ' was leaked ! Please fix your application to end transactions in progress properly and to close the database When it is no longer needed . "
I already tried to use the FragmentStatePagerAdapter without success.
Could you kindly tell me how to proceed ? I do not want the Fragment is regenerated each time, causing problems to the database. Have you got an example for save Fragment/View sate?
I have not found any suggestion for now .
thank you very much

use setOffscreenPageLimit property for your ViewPager object.
ViewPager pager = (ViewPager) findViewById(R.id.viewPagerId);
pager.setOffscreenPageLimit(2);

First of all create a new instance of Fragment every time the getItem() is called
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return new FragmentList();
case 1:
return new AnalyzeFragmentPage1();
case 2:
return new AnalyzeFragmentPage2();
}
return null;
}
Note that FragmentPagerAdapter does not work exactly like normal Adapter meaning it does NOT use an empty vessel Fragment and then refills it with different data.
Instead it creates a new Fragment whenever it is needed. So you should pass data (if any) when the Fragment is created. Please study the example in the Android docs.
In a real time projects instead of passing current position to a new Fragment you could pass the actual ID of the entry that a Fragment should refer to.

Related

FragmentPagerAdapter Fragment getItem wrong position?

So for some reason i'm getting a wrong position when i switch tabs on my tabbed activity.
At the bottom of my post you can see my logcat when i start my app, tab 1 and tab 2 their oncreateview is called.
And position 0 switches to 1 even though i'm on tab 1 which must be position 0.
So my problem is that when i change a tab, the wrong data is shown and wrong logcat info.
for example i change to tab 2, logcat shows oncreateview i called from tab3 ?
How can i fix this?
Thanks,
public class SectionsPagerAdapter extends FragmentPagerAdapter {
private int numOfTabs;
SectionsPagerAdapter(FragmentManager fm, int numOfTabs) {
super(fm);
this.numOfTabs = numOfTabs;
}
#Override
public Fragment getItem(int position) {
switch (position){
case 0:
Log.e(TAG, "Pos: "+ position);
return new Tab1();
case 1:
Log.e(TAG, "Pos: "+ position);
return new Tab2();
case 2:
Log.e(TAG, "Pos: "+ position);
return new Tab3();
case 3:
Log.e(TAG, "Pos: "+ position);
return new Tab4();
case 4:
Log.e(TAG, "Pos: "+ position);
return new Tab5();
default:
return null;
}
}
#Override
public int getCount() {
// Get total pages
return numOfTabs;
}
}
Logcat
.activities.MainActivity: Pos: 0
.activities.MainActivity: Pos: 1
.fragments.Tab1: onCreateView called!
.fragments.Tab2: onCreateView called!
ViewPager by default loads 2 tabs. So getItem gets called twice when you first open the app. When you go to tab2, ViewPager loads tab3 in advance so that the scroll will be smooth. See offscreenPageLimit.

How to refresh Fragment and pass info based on its position?

I`ve got an activity with ViewPager. There is 1 Fragment class - ScheduleFragment and a BroadcastReceiver in it. What I need is to get info and fill up the list in the fragment, based on its position.
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
ScheduleFragment scheduleFragment = new ScheduleFragment();
return scheduleFragment.newInstance(position + 1);
}
#Override
public int getCount() {
// Show 5 total pages.
return 5;
}
#Override
public int getItemPosition(Object object) {
return super.getItemPosition(object);
}
#Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return getString(R.string.descr_monday);
case 1:
return getString(R.string.descr_tuesday);
case 2:
return getString(R.string.descr_wednesday);
case 3:
return getString(R.string.descr_thursday);
case 4:
return getString(R.string.descr_friday);
}
return null;
}
And here the ScheduleFragment`s methods:
public ScheduleFragment newInstance(Integer day) {
ScheduleFragment fragment = new ScheduleFragment();
Bundle args = new Bundle();
args.putInt("day", day);
fragment.setArguments(args);
return fragment;
}
private class ScheduleBroadcastReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
ArrayList<Lesson> result = (ArrayList<Lesson>) intent.getSerializableExtra("schedule");
ArrayList<Lesson> dataToInsert = new ArrayList<>();
Log.i("ASSSSSSSSSSSS", day.toString());
for (Lesson lesson : result) {
if (Objects.equals(lesson.getDay(), day)) {
dataToInsert.add(lesson);
}
}
adapter = new ScheduleListAdapter(context, result);
SwingBottomInAnimationAdapter swingBottomInAnimationAdapter = new SwingBottomInAnimationAdapter(adapter);
swingBottomInAnimationAdapter.setAbsListView(schedule_listView);
schedule_listView.setAdapter(swingBottomInAnimationAdapter);
adapter.notifyDataSetChanged();
}
}
I also have a Spinner in Toolbar with weeks. So also I want to update the fragment when I pick another week.
Please, guys. Help me. I`ve tried some solutions, but none of them helped me!
Hope for you assistance
I'm probably not an expert on this, but I just completed a project full of fragments. The 'dirty' way I passed data between them was accomplished by using a singleton class in my project. This probably isn't the best solution, though.
For instance, my project included a fragment which I updated based on options in a settings activity. The FamilyMap (familyMap) class is my singleton, and I kept a settings model containing maps of needing information within that object. The code shown here is updating that settings model based on user interaction.
public void mapTypeSelection(String selection)
{
Log.d("Map Selection", selection);
switch(selection)
{
case "Normal":
familyMap.getMapFrag().getmMap().setMapType(GoogleMap.MAP_TYPE_NORMAL);
familyMap.getSettings().setMapType(new Pair<String, Integer>("Normal", GoogleMap.MAP_TYPE_NORMAL));
break;
case "Satellite":
familyMap.getMapFrag().getmMap().setMapType(GoogleMap.MAP_TYPE_SATELLITE);
familyMap.getSettings().setMapType(new Pair<String, Integer>("Satellite", GoogleMap.MAP_TYPE_SATELLITE));
break;
case "Hybrid":
familyMap.getMapFrag().getmMap().setMapType(GoogleMap.MAP_TYPE_HYBRID);
familyMap.getSettings().setMapType(new Pair<String, Integer>("Hybrid", GoogleMap.MAP_TYPE_HYBRID));
break;
case "Terrain":
familyMap.getMapFrag().getmMap().setMapType(GoogleMap.MAP_TYPE_TERRAIN);
familyMap.getSettings().setMapType(new Pair<String, Integer>("Terrain", GoogleMap.MAP_TYPE_TERRAIN));
break;
default:
break;
}
}
//private method of your class
private int getIndex(Spinner spinner, String myString)
{
int index = 0;
for (int i=0;i<spinner.getCount();i++){
if (spinner.getItemAtPosition(i).toString().equalsIgnoreCase(myString)){
index = i;
break;
}
}
return index;
}
public Pair<String, Integer> colorLineSelection(String selection)
{
Log.d("Map Selection", selection);
switch(selection)
{
case "Red":
return new Pair<String, Integer>("Red", Color.RED);
case "Blue":
return new Pair<String, Integer>("Blue", Color.BLUE);
case "Green":
return new Pair<String, Integer>("Green", Color.GREEN);
default:
return null;
}
}
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
switch(parent.getId()){
case R.id.mapTypeSpinner:
mapTypeSelection((String)parent.getItemAtPosition(position));
break;
case R.id.lifeLineSpinner:
FamilyMap.getInstance().getSettings().setLifeColor(colorLineSelection((String)parent.getItemAtPosition(position)));
familyMap.getMapFrag().drawLines();
break;
case R.id.spouseLineSpinner:
FamilyMap.getInstance().getSettings().setSpouseColor(colorLineSelection((String) parent.getItemAtPosition(position)));
familyMap.getMapFrag().drawLines();
break;
case R.id.familyLineSpinner:
FamilyMap.getInstance().getSettings().setFamilyColor(colorLineSelection((String) parent.getItemAtPosition(position)));
familyMap.getMapFrag().drawLines();
break;
default:
break;
}
}
The other thing I did was I retained the instance of my fragment - it was the main portion of my app, so it made sense for my situation. That allowed me to be able to update the original fragment when an option was selected. I also set the fragment to update when a certain result was reached by using startActivityForResult().
I think a better (cleaner, more acceptable) option for sending the data to your fragment would be object serialization, but at least in my case, that proved to be more work than I could afford at the time.
Hopefully this at least helps to point you in the right direction!
So, after trying, trying and a lot of trying I figured it out!
In newInstance method, where I create new instance of Fragment I`ve added a bundle with day, according to fragments position
public ScheduleFragment newInstance(Integer day) {
ScheduleFragment fragment = new ScheduleFragment();
Bundle args = new Bundle();
args.putInt("day", day);
fragment.setArguments(args);
return fragment;
}
Then, in fragment's onCreateView I get this argument with getArguments().getInt("day"); and put it into private Integer dayglob; field, which means, that its different for every fragment.
Now, when I have 'day' variable in my fragment I can put info into list according to provided day:
private class ScheduleBroadcastReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
ArrayList<Lesson> result = (ArrayList<Lesson>) intent.getSerializableExtra("schedule");
ArrayList<Lesson> dataToInsert = new ArrayList<>();
for (Lesson lesson : result) {
if (Objects.equals(lesson.getDay(), dayglob)) {
dataToInsert.add(lesson);
}
}
adapter = new ScheduleListAdapter(context, dataToInsert);
SwingBottomInAnimationAdapter swingBottomInAnimationAdapter = new SwingBottomInAnimationAdapter(adapter);
swingBottomInAnimationAdapter.setAbsListView(schedule_listView);
schedule_listView.setAdapter(swingBottomInAnimationAdapter);
try {
getActivity().unregisterReceiver(mScheduleBroadcastReceiver);
} catch (IllegalArgumentException e) {
Log.i("Unregister", e.toString());
}
}
}
Every fragment got its own "onReceive" and wor on data from service for specific fragment.
Also, about the week spinner and updating the lists according to its value: I've made a static field public static Spinner week_spinner;, initialized it it onCreate method of my Activity and then, in fragment's "onCreateView" I made a request to my database like this:
requestQueue.add(connectionManager.getSchedule(this, savedUser.getGroup(), MainActivity.week_spinner.getSelectedItemPosition() + 1));
The only matter thing here is getting value by accessing spinner via static reference.
And, also, I've set Listener which says to fragments something like
Hey, guys! You are not up to date! Update yourself!
week_spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
mViewPager.getAdapter().notifyDataSetChanged();
}
});
And a little trick which make it possible is to change getItemPositionin FragmentPagerAdapter class (SectionsPagerAdapter in my case)
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
It`s kinda tricky, I guess, but its the only way i can figure it out
I hope, this will help someone!

Getting currently selected tab of a tabbed activity

So I am currently developing an app which displays a json file via cards in the main activity. Depending on which tab is currently selected in my tabbed activity, a method which downloads the json file decides what json file to download (I pass an integer and there is a switch in the method).
Here is the method:
Fragment.Downloadjson(rootview,integer,context);
Now, for my tabbed Activity I have a SectionsPagerAdapter which has the usual stuff: getItem, getCount, and getPageTitle.
In getItem I am creating my new fragments:
#Override
public Fragment getItem(int position) {
View v1 = getWindow().getDecorView().getRootView();
switch (position) {
case 0:
//Fragment.Download(v1,0,getApplicationContext());
return new Fragment().f(Fragment.page.TODAY);
case 1:
//Fragment.Download(v1,1,getApplicationContext());
return new Fragment().f(Fragment.page.TOMORROW);
default:
return new Fragment();
}
}
Exception :
android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application
I have found the source of this error to be the rootview parameter in my method, because it works in onCreateView with rootView as a parameter, because I define it there. Hovewer, I cannot make an if statement for the currently selected tab or currently displayed fragment there, because
a) I don't know how to get the currently selected tab
b) I'm not sure it would download the json file again after I switch the tab, because after all, the If statement would be in onCreateView
So, my question is,
how do I solve this?
Don't do it inside getItem() method. In getItem just create the fragment.
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return new MyFragment0();
case 1:
return new MyFragment1();
case 2:
return new MyFragment2();
}
return null;
}
Override the method instantiateItem and in there keep a map of fragment and their position:
private ArrayMap<Integer, MyFragmentBaseClass> mPagerFragmentMap = new ArrayMap<>();
#Override
public Object instantiateItem(ViewGroup container, int position) {
MyFragmentBaseClass fragment = (MyFragmentBaseClass) super.instantiateItem(container, position);
mPagerFragmentMap.put(position, fragment);
return fragment;
}
note MyFragmentBaseClass can be a marker interface that all fragments implement.
With the code above, you can already map each fragment to its tab.
If you're using TabLayout you can now set a listener using setOnTabSelectedListener and use one of its methods onTabSelected to know when the user selects that tab and perform any operation you want.

Android ViewPager: Destroy Fragment on Slide?

I am basically playing an animation on each fragment of the view pager. The animation plays when the user slides to the specific fragment. However, certain fragments don't play the animation the second time I visit them. That's because the view pager keeps them in memory.
I need to destroy each fragment after the user slides to another fragment. This way, the animations play every time I revisit those fragments.
Main View:
pager = (ViewPager) findViewById(R.id.guidepager);
mAdapter = new NewUserGuideAdapter(getSupportFragmentManager());
pager.setAdapter(mAdapter);
pager.setOffscreenPageLimit(0); //Tried this too. Didnt work
Fragment:
public class NewUserPage_Two extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.activity_new_user_page__two, container, false);
//Play animation, etc
Animation animation_1 = AnimationUtils.loadAnimation(NewUserPage_Two.this.getActivity(), R.anim.abc_slide_in_bottom);
person1.setAnimation(animation_1);
return rootView;
}
Adapter:
public class NewUserGuideAdapter extends FragmentPagerAdapter {
public NewUserGuideAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int index) {
switch (index) {
case 0:
return new NewUserPage_One();
case 1:
return new NewUserPage_Two();
case 2:
return new NewUserPage_Three();
case 3:
return new NewUserPage_One();
case 4:
return new NewUserPage_One();
}
return null;
}
#Override
public int getCount() {
// get item count - equal to number of tabs
return 5;
}
}
How can I amend my code guys?
ViewPager provide a method mViewPager.setOffscreenPageLimit(0);
Set the number of pages that should be retained to either side of the current page in the view hierarchy in an idle state. Pages beyond this limit will be recreated from the adapter when needed.
Try this inside the fragment:
((BaseAdapter) *YourContainer*.getAdapter()).notifyDataSetChanged();
You can refer to: Refresh Current Fragment (ListView Data) remaining in the same activity
public void setOffscreenPageLimit(int limit) {
if (limit < DEFAULT_OFFSCREEN_PAGES) {
Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +
DEFAULT_OFFSCREEN_PAGES);
limit = DEFAULT_OFFSCREEN_PAGES;
}
if (limit != mOffscreenPageLimit) {
mOffscreenPageLimit = limit;
populate();
}
}
That's the method body,DEFAULT_OFFSCREEN_PAGES=1by the way.I think google add this limits cause you need at least the 2 views between current item while you are sliding.
You can try using addOnPageChangeListener() and start your animation on onPageSelected.

Android Fragment issue

i'm new to Android, (not programming, or even Java) so bear with me.
I'm trying to get a handle on the use of fragments.
I've got a project that I've created using the default swipe/actionbar. I've extended this further to handle the settings i want.... however i don't quite understand what's going on/how to fix this.
/**
* A {#link FragmentPagerAdapter} that returns a fragment corresponding to
* one of the sections/tabs/pages.
*/
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
// getItem is called to instantiate the fragment for the given page.
// Return a DummySectionFragment (defined as a static inner class
// below) with the page number as its lone argument.
Fragment fragment = new DummySectionFragment();
Bundle args = new Bundle();
args.putInt(DummySectionFragment.ARG_SECTION_NUMBER, position + 1);
fragment.setArguments(args);
return fragment;
}
#Override
public int getCount() {
// Show 8 total pages.
return 8;
}
#Override
public CharSequence getPageTitle(int position) {
Locale l = Locale.getDefault();
switch (position) {
case 0:
return getString(R.string.title_section1).toUpperCase(l);
case 1:
return getString(R.string.title_section2).toUpperCase(l);
case 2:
return getString(R.string.title_section3).toUpperCase(l);
case 3:
return getString(R.string.title_section4).toUpperCase(l);
case 4:
return getString(R.string.title_section5).toUpperCase(l);
case 5:
return getString(R.string.title_section6).toUpperCase(l);
case 6:
return getString(R.string.title_section7).toUpperCase(l);
case 7:
return getString(R.string.title_section8).toUpperCase(l);
}
return null;
}
}
/**
* A dummy fragment representing a section of the app, but that simply
* displays dummy text.
*/
public class DummySectionFragment extends Fragment {
/**
* The fragment argument representing the section number for this
* fragment.
*/
public String ARG_SECTION_NUMBER = "section_number";
public DummySectionFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
int position;
position = getArguments().getInt(ARG_SECTION_NUMBER)-1;
View rootView;
TextView dummyTextView;
I don't really want anything static or final here, and I've got it mostly worked out but I don't understand the following line or how to fix it. I kinda get what it's doing.
args.putInt(DummySectionFragment.ARG_SECTION_NUMBER, position + 1);
The error is: cannot make a static reference to the non-static field DummySectionFragment.ARG_SECTION_NUMBER
There is probably a simple fix for this, i just am unfamiliar enough with Android and Java, as my current job i spend all my time in SQL Server.
-- EDITED ADDITIONS
i'm not opposed to anything static or final etc. the problem i'm not quite understanding is when i want to DO something in each of those fragments. I have a textview on each of those layouts and i want to be able to manipulate them say in a loop. I think i'm stuck in a circle and can't figure my way out... lol.
For example below the code I put above is
case 4:
rootView = inflater.inflate(R.layout.fragment_main_location,container, false);
dummyTextView= (TextView) rootView .findViewById(R.id.section_label);
// location
Button btnShowLocation = (Button) rootView.findViewById(R.id.btnShowLocation);
Button btnShowDBLocList = (Button) rootView.findViewById(R.id.btnShowDBLocList);
Button btnLocationsCount = (Button) rootView.findViewById(R.id.btnLocationsCount);
Button btnTruncateDBLocationsTable = (Button) rootView.findViewById(R.id.btnTruncateDBLocationsTable);
btnTruncateDBLocationsTable.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Activity activity = getActivity();
int intCount = 0;
/*if (activity != null) {
//dummyTextView.setText("");
try {
locationDatabaseHandler.truncateLocationTable();
intCount = locationDatabaseHandler.getLocationCount();
} catch (Exception e){
//dummyTextView.append(e.toString());
}
//dummyTextView.append("Count:" + intCount + "\n\n");
Toast.makeText(activity, "toast_you_just_clicked_a_fragment btnTruncateDBLocationsTable button", Toast.LENGTH_LONG).show();
}*/
}
});
dummyTextView = (TextView) rootView .findViewById(R.id.section_label);
dummyTextView.append("\nLocation Stuff\n");
break;
//dummyTextView.append("Count:" + intCount + "\n\n");
I run into a circle where if I dummyTextView try to use the dummmyText w/in the onClick event, it says that i need to make it static (quick fix) with a complaining error of : cannot refer to a non-final variable dummy7Text inside an indder class defined in a different method.
I've added a variables to handle this inside the onCreate that get filled for (LayoutInflater and Viewgroup, and then reference them w/in the onclick (not shown), but when i go in and instansiate ... nothing happens with the textviews...
There is something i'm not quite getting here, and once i get by that hurdle, i'll have this by the balls, and will be able to make it do what i want.
I don't really want anything static or final here
Why? They will not negatively impact performance, nor are they a sign of poor coding practices.
I don't understand the following line
Every Fragment can be created with a Bundle containing any number of key-value pairs. DummySectionFragment.ARG_SECTION_NUMBER is a the key (a String), and position + 1 is the value. Thus this code is telling the new DummySectionFragment which section of content the Fragment should show.
This method is preferable to putting these arguments in a constructor because your custom constructor for a Fragment isn't guaranteed to be called. There are many ways for Android to generate Fragments, so this lowers the possibility of problems such as NullPointerExceptions.
the error is: cannot make a static reference to the non-static field DummySectionFragment.ARG_SECTION_NUMBER
As you seem to know, DummySectionFragment.ARG_SECTION_NUMBER is referring to a static field within the DummySectionFragment class called ARG_SECTION_NUMBER. By making this field non-static, you can no longer reference this constant value without a DummySectionFragment instance.
Another option (if you really don't want a static field) would be to hardcode the String. Thus your code would be:
args.putInt("section_number", position + 1);
However, a public static field is a much better coding practice and will prevent silly mistakes with typos in your Strings.
I run into a cirle where if i dummyTextView try to use the dummmyText w/in the onClick event, it says that i need to make it static (quick fix) with a complaining error of : cannot refer to a non-final variable dummy7Text inside an indder class defined in a different method.
Instead of using an anonymous inner class, I would let your Fragment implement OnClickListener.
For example:
public class MyFragment extends Fragment implements OnClickListener {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// ...
Button btnTruncateDBLocationsTable = (Button) rootView.findViewById(R.id.btnTruncateDBLocationsTable);
btnTruncateDBLocationsTable.setOnClickListener(this);
// ...
}
#Override
public void onClick(View v) {
// You can reference dummyTextView here without any problems
}
}
that means that ARG_SECTION_NUMBER should be declared as public static. Better if it declared as public static final

Categories

Resources