Android - How to restore state of ViewPager's items - java

I'm having trouble with restoring state of a View inside a ViewPager. The content of the ViewPager is a view extending FrameLayout.
The problem is the FrameLayout.onRestoreInstanceState() is not being called if added programmatically into the ViewPager
Here's the code of my Activity.java
private ViewPager vPager;
private MainPagerAdapter mAdapter;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
setContentView(R.layout.activity_layout);
// all the findViewById stuff
CustomView cv1 = new CustomView(this);
CustomView cv2 = new CustomView(this);
cv1.setId(R.id.custom_view_id_1);
cv2.setId(R.id.custom_view_id_2);
mAdapter = MainPagerAdapter();
mAdapter.addView(cv1);
mAdapter.addView(cv2);
vPager.setAdapter(mAdapter);
}
MainPagerAdapter is a class from the accepted answer of this question
Source code for CustomView.java
#Override
protected Parcelable onSaveInstanceState() {
Log.d(TAG, "onSaveInstanceState() called");
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
Log.d(TAG, "onRestoreInstanceState() called");
}
Here's my findings so far:
onSaveInstanceState() will be called but onRestoreInstanceState() is not
When I tried to add the View directly to the root of the Activity, it calls both functions.
I found out that Activity's onRestoreState function will be called before the ViewPager calls the adapter's instantiateItem() function. So when the activity restore its state, the ViewPager doesn't have any children yet, thus the savedState doesn't belong to anyone
So I figure out that I need to make one of two things to work:
Make sure the ViewPager instantiate the item before trying to restore the state, or
Calls the CustomView's onRestoreInstanceState() manually.
I somehow managed to make option number 2, but is there any way to do option number 1?

If I understood your question, you can save your ViewPager items state using mPage.setOffscreenPageLimit(4); 4 is the number of my Fragments inside ViewPager.

Related

Prevent DialogFragment from sending the app into energy save mode

I'm creating an App for an Android Wearable Device.
When showing a custom layout as a dialogue fragment, the app will get minimized (getting replaced by the watch face) after about 30 seconds. I know that this is supposed to be the default behavior of apps out of touch with their users, but in my case, the app needs to stay visible even if not touched four minutes.
The Activities calling it did get these instructions inside their onCreate implementation.
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
that works like a charm and in the way I want it. However, the DialogFragment that serves as an error notification (and may need to get observed without touching it for some time) does not obey this setting.
I tried to get the flag inside the DialogFragment too by placing it inside the onViewCreated calls, but it does not have the getWindow Method. While the next code segment is valid, is does not work either.
getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
The way the dialog fragment is called looks like this. That's the code to call it from the FragmentActivity needing to show it. As you might notice, there is a field "activity", which is because the whole call is done from a static class outside the activity. I want to call the same DialogFragment from multiple activities, with only the text and the title being different.
public static void showDialogCuston(String title, String message, FragmentActivity activity){
ErrorDialogFragment edf = ErrorDialogFragment.newInstance(title,message);
FragmentManager fm = activity.getSupportFragmentManager();
edf.show(fm, "TAG");
}
And that's what the DialogFragment does look like inside. I kicked out all TextView text assignments because I doubt that they could offer any kind of information about that request, and just making the code fragment less readable.
public class ErrorDialogFragment extends DialogFragment {
//some private text views
public ErrorDialogFragment(){
}
public static ErrorDialogFragment newInstance(String title, String text){
ErrorDialogFragment edf = new ErrorDialogFragment();
Bundle args = new Bundle();
args.putString("title",title);
args.putString("text",text);
edf.setArguments(args);
return edf;
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_error, container, false);
// R.layout.fragment_error is the layout that serves as my custom dialog
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
//assigning layout elements to private fields
//assigning stuff from the bundle inside some textViews
closeButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
dismiss();
//playing a sound from SoundPool and doing haptic feedback on button press
}
});
}
}
So, all I want is that the app won't disappear by itself when one is staring too long on the DialogFragment without touching it.
In your case, I would like to suggest keeping the screen-on flag in the dialog layout file so that when the dialog view is visible, it keeps the screen on. Check the developer's documentation for more information. However, I am adding the code from the documentation for convenience.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:keepScreenOn="true">
...
</RelativeLayout>
Set the android:keepScreenOn to true in the root element of your dialog layout. And I hope you also have the following permission in your AndroidManifest.xml file.
<uses-permission android:name="android.permission.WAKE_LOCK" />

onCreateView for tab being called whenever selecting adjacent tab?

I am having a weird issue where onCreateView is being called every time I navigate to an adjacent tab in my TabLayout. Here is my code:
news_feed.java:
private static TabLayout tabLayout;
#Override
public void onCreate(Bundle savedInstanceState) {
tabLayout = (TabLayout) findViewById(R.id.tab_layout);
tabLayout.addTab(tabLayout.newTab().setText("Public"));
tabLayout.addTab(tabLayout.newTab().setText("Friends"));
tabLayout.addTab(tabLayout.newTab().setText("My Tabs"));
tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
final ViewPager viewPager = (ViewPager) findViewById(R.id.pager);
final PagerAdapter adapter = new PagerAdapter
(getSupportFragmentManager(), tabLayout.getTabCount());
viewPager.setAdapter(adapter);
viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout));
tabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
#Override
public void onTabSelected(TabLayout.Tab tab) {
viewPager.setCurrentItem(tab.getPosition());
}
#Override
public void onTabUnselected(TabLayout.Tab tab) {
}
#Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
}
PublicTab.java:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
System.out.println("Creating a new view");
fragmentView = inflater.inflate(R.layout.public_tab, container, false);
progressOverlay = fragmentView.findViewById(R.id.progress_overlay);
AndroidUtils.animateView(progressOverlay, View.VISIBLE, 0.9f, 200);
getPublicPosts(progressOverlay, fragmentView);
return fragmentView;
}
I have 3 other tabs that look exactly like PublicTab.java but with different layouts. I am not sure why onCreateView is being called so frequently. I thought in the lifecycle, onCreateView is only invoked for 2 reasons:
1. when we first initialize the tabs
2. if we come out from onStop or onPause method.
However, when switching between the tabs that are next to the actual tab, the println message is called like above and this is not what I want because I don't want to update the View of that fragment so frequently: only during the first 2 reasons above. Anyone know why this is happening? Any help would be appreciated! Thanks!
Use setoffScreenPageLimit
Android documentation:
public void setOffscreenPageLimit (int limit)
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
The default value is set to 1.
First and foremost of all, Viewpager default working property is this.
if you have around ten tabs and you are choice second tab.
viewpager will create tab2, tab1 and tab 3. This to provide smooth navigation between viewpagers. So whatever you do, your oncreate view will get called, because it will recreate the fragment everytime.
One thing you can do, is if you don't wan't to make a network call/or fetch data from online everytime a tab has been revisited after it's first creation.
you can do this.
save the data in TabLayout activity, use an interface create a setdata and getData method.
so whenever it reaches onCreateview. check if there is any data left in your get method, passing position to the method.
if exist display that data, else make a network call.

Does fragment call the onCreate method of parent activity?

I have an activity with a viewPager inside of it, and a static ArrayList of integers that I am shuffling using Collections.shuffle(list) in the activity's onCreate method, this viewPager's fragments are using the ArrayList in parent activity.
The problem is that whenever a new fragment instantiated of the viewPager the onCreate method of parent activity is called, and I don't want that to happen because I want the list to have the same data in all fragments and not reshuffled. Do fragments call the onCreate method of their parent activities everytime there is a new instance? if Yes how can I work around this to keep the list from shuffling every time?
CODE:
Activity Code:
public static final ArrayList<Integer> IDs = new ArrayList<>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
IDs.add(0);
IDs.add(1);
IDs.add(2);
Collections.shuffle(IDs);
setContentView(R.layout.activity_walkthrough);
pager = (ViewPager) findViewById(R.id.pager);
adapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
pager.setAdapter(adapter);
Fragment Code:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = (View) inflater.inflate(
R.layout.fragment_walkthrough, container, false);
final TypedArray imgs = getResources().obtainTypedArray(R.array.walkthrough_images);
ImageView imageView = (ImageView) v.findViewById(R.id.image);
if (page == 0) {
imageView.setImageResource(imgs.getResourceId(Walkthrough.IDs.get(0), 0));
} else if (page == 2) {
imageView.setImageResource(imgs.getResourceId(Walkthrough.IDs.get(1), 0));
} else {
imageView.setImageResource(imgs.getResourceId(Walkthrough.IDs.get(2), 0));
}
return v;
}
Now I want the ArrayList "IDs" to always have the same data and order when ever I instantiate a new fragment but it is not working, every time I create a new fragment the method onCreate gets recalled and a reshuffle happens!
Fragments are added to activity and therefore fragments get affected by activity.
Activity can cause calling any fragment callback method, but fragment can't
The lifecycle of the activity in which the fragment lives directly affects the lifecycle of the fragment.
For example, when the activity receives onPause(), each fragment in the activity receives onPause().
Fragments have a few extra lifecycle callbacks, however, that handle unique interaction with the activity in order to perform actions such as build and destroy the fragment's UI.
These additional callback methods are like onAttach(), onCreateView(), etc.
It'll clears the somewhat relation between fragment and activity.
Thanks

Nested ViewPager returns null after rotation

I need a reference to VerticalViewPager in PageFragSanningellerKonsekvens.
I got two ViewPagers: one for horizontal swiping that goes on for ever, and one for vertical swiping that loads three fragments (mid, swipe up, swipe down) that loads content. The horizontal ViewPager loads a new instance of VerticalViewPager on every swipe. I need a reference in the actual fragment that is loaded by the VerticalViewPager, so that I can swipe vertically by tapping a button. I also need a reference to the horizontal ViewPager to allow the first page to have a button that triggers a horizontal swipe.
I've solved it by sending the reference as an argument like this:
FragSanningEllerKonsekvens - The class that initializes the VerticalViewPager.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_sanning_eller_konsekvens, container, false);
initComponents();
setComponents();
return view;
}
private void initComponents() {
verticalViewPager = (VerticalViewPager) view.findViewById(R.id.verticalviewpager);
mStatementPagerAdapter = new TruthDarePagerAdapter(getChildFragmentManager()).newInstance(getChildFragmentManager(),mViewPager, verticalViewPager);
}
Its transfers in TruthOrDarePagerAdapter:
private ViewPager mViewPager;
private VerticalViewPager mVerticalViewPager;
public static TruthDarePagerAdapter newInstance(FragmentManager fm, ViewPager mViewPager, VerticalViewPager mVerticalViewPager){
TruthDarePagerAdapter horizontalPagerAdapter = new TruthDarePagerAdapter(fm);
horizontalPagerAdapter.setViewPager(mViewPager);
horizontalPagerAdapter.setVerticalViewPager(mVerticalViewPager);
return horizontalPagerAdapter;
}
public VerticalViewPager getVerticalViewPager() {
return mVerticalViewPager;
}
public void setVerticalViewPager(VerticalViewPager mVerticalViewPager) {
this.mVerticalViewPager = mVerticalViewPager;
}
private void setViewPager(ViewPager mvViewPager){
this.mViewPager = mvViewPager;
}
private ViewPager getViewPager(){
return mViewPager;
}
#Override
public Fragment getItem(int position) {
PageFragSanningellerKonsekvens frag = new PageFragSanningellerKonsekvens().newInstance(getViewPager(), getVerticalViewPager());
.
.
.
return frag;
}
PageFragSanningellerKonsekvens - The class that needs the reference.
public static PageFragSanningellerKonsekvens newInstance(ViewPager mViewPager, VerticalViewPager mVerticalViewPager){
PageFragSanningellerKonsekvens frag = new PageFragSanningellerKonsekvens();
frag.setViewPager(mViewPager);
frag.setVerticalViewPager(mVerticalViewPager);
return frag;
}
private void setupMenu() {
viewTop = view.findViewById(R.id.sek_top);
viewBottom = view.findViewById(R.id.sek_bottom);
viewTop.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
getVerticalViewPager().setCurrentItem(0, true);
}
});
viewBottom.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
getVerticalViewPager().setCurrentItem(2, true);
}
});
}
The code above works excellent until I rotate the device. After rotation the getVerticalViewPager() returns null. In other parts of the application I've used this pattern without any problems: on rotation the ViewPager is sent as a parameter and the application reloads without dropping the reference.
So the conclusion I've drawn is that the VerticalViewPager doesn't get reinitialized on rotation, while PageFragSanningellerKonsekvens is.
My question is; how can I solve it? Can I force it to be reinitialized, or can I obtain a reference to it in some other way?
EDIT 1: I'll think its related to getItem() in the FragmentStatePagerAdapter. It will be called to allocate 3 fragments at every given time. I think this might nullify the getVerticalViewPager() method.
EDIT 2: I moved some code to allow the ViewPager and the button to be initialized in the same class, thus making it somewhat null-proof. Thanks for the help!
Ok this might not be a complete answer but I think it would be too long a comment.
I think you are right that the null pointer happens at getItem in the adapter. (a logcat report to confirm this would be nice).
The reason for this (I think) is that the adapter tries to recreate the inner fragments before the inflation of the ViewPager is complete. Normally I create similar adapters in onCreate because it normally do not rely on anything but a fragmentmanager.
Having this in mind, and the fact that you might have a hard time avoiding the null pointer in the current design, I propose a new design.
Am only at my phone right now so is hard to give you the code I have in mind. For now I just sketch the idea:
Move the getter/setter of ViewPagers from the adapter to FragSanningEllerKonsekvens, leaving the creation of TruthOrDarePagerAdapter only depend on fragment manager
Now do the same for PageFragSanningellerKonsekvens making it simply PageFragSanningellerKonsekvens.newInstance() in getItem of the adapter
Finally, you are now missing the reference to the ViewPagers in PageFragSanningellerKonsekvens PageFragSanningellerKonsekvens, but remember you now have the getters of both ViewPagers in the parent FragSanningEllerKonsekvens
So the buttons now become:
((FragSanningEllerKonsekvens)getParentFragment()).getVerticalViewPager().setCurrentItem(0, true);
And
((FragSanningEllerKonsekvens)getParentFragment()).getVerticalViewPager().setCurrentItem(2, true);

Creating an android fragment that opens another fragment based on button activity

I'm trying to create an activity that adds a dynamic fragment at runtime. From that fragment I want to be able to open six other fragments on button click. [Going to use a case to implement this most likely]
Think of it as a windows 8 UI; with 6 buttons, each one opens a new fragment.
Unfortunately I have no idea how to go about this. I can't seem to get the button to pass data back to the main activity. I've also lost quite a bit of my code due to a git mishap. Here's what I recreated.
If you have any tips on coding style, syntax, java, OO- those are all welcome too. I'm coming from a C background. My end goal would be to create a replaceFragment(Frag) method for some easy syntactic sugar later on. Though I couldn't implement that with any success so far.
Another small question with fragments - I'm trying to add them dynamically at run-time - do I need to create all of them at run time? So each one needs a .add [Drink fragment, Menu fragment] or do I just need to do the .replace
SingleFragmentActivity.java
public abstract class SingleFragmentActivity extends FragmentActivity{
protected abstract Fragment createFragment();
FragmentManager fm = getSupportFragmentManager();
#Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); //Lock screen orientation for app
Fragment frag = fm.findFragmentById(R.id.fragment_container);
fm.beginTransaction()
.add(R.id.fragment_container,frag)
.commit();
}
}
Customer_Activity.java
public class Customer_Activity extends SingleFragmentActivity {
public static Context appContext;
#Override
protected Fragment createFragment() {
return new CustomerSelectionFragment();
}
}
CustomerSelectionFragment
public class CustomerSelectionFragment extends Fragment implements OnClickListener{
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.customer_selection_fragment, container, false);
//Buttons should be placed here?
Button btnDrink = (Button) v.findViewById(R.id.Drink);
btnDrink.setOnClickListener(this);
Button btnMenu = (Button) v.findViewById(R.id.Menu);
btnDrink.setOnClickListener(this);
return v;
}
//implement the onClick method here
public void onClick(View v) {
// Perform action on click
switch(v.getId()) {
case R.id.Drink:
//Not sure how to pass "Create Drink Fragment to activity?
break;
case R.id.Menu:
//Pass Create Menu fragment to activity?
break;
}
}
}
Totally ok with people editing my post for good-faith reasons [clarity, etc].
Any communication between fragments should be done via activity . Here is the link to developers site http://developer.android.com/training/basics/fragments/communicating.html , the tutorial is about communicating between fragments and pretty much explains everything.

Categories