Android Fragment from Layout - Retain variable data - android

I've been searching for hours and tried numerous methods but cannot seem to grasp my head around the idea / figure out how to retain/restore data in a ViewPager Fragment when it is destroyed and then recreated.
Here is what I have -
An activity where I setup the ViewPager and PageAdapter
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.view_pager);
//Setup pager and adapter
mPager = (ViewPager) findViewById(R.id.viewpager);
mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
mPager.setAdapter(mPagerAdapter);
}
My PageAdapter where I setup a fragment with a bundle using .newInstance()
#Override
public Fragment getItem(int position) {
ScreenSlidePageFragment fragment = new ScreenSlidePageFragment();
return fragment.newInstance(position);
}
My Fragment that has a layout that includes a TextView that shows the user a question, a picture, and two True/False buttons. New instance is returned back to the Adapter.
public static ScreenSlidePageFragment newInstance(int position) {
ScreenSlidePageFragment fragment = new ScreenSlidePageFragment();
Bundle args = new Bundle();
args.putInt("page_position", position + 1);
fragment.setArguments(args);
return fragment;
}
//the fragment is newly created for the first time or recreated when exiting the view
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_main, parent, false);
//Handle a question being displayed on each fragment
count = getArguments().getInt("page_position") - 1;
mQuestionText = (TextView)v.findViewById(R.id.questionText);
mQuestionText.setText(bank.get(count).getQuestion());
//change the image depending on correct / incorrect answer
mPhoto = (ImageView)v.findViewById(R.id.imageView);
trueButton = (Button)v.findViewById(R.id.true_button);
falseButton = (Button)v.findViewById(R.id.false_button);
//True Button is pressed
trueButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if(bank.get(count).getAnswer()) {
mPhoto.setImageResource(R.drawable.right);
clickable = false;
}
else {
mPhoto.setImageResource(R.drawable.wrong);
clickable = false;
}
trueButton.setClickable(clickable);
falseButton.setClickable(clickable);
}
});
What I cannot figure out for the life of me, is how to retain/save that fact that the user has pressed a button and which picture to display when the fragment is restored. I have tried a number of options using onResume(), getArguments(), onSaveInstanceState(), onActivityCreated() etc but none of them seem to work.
I can fix the problem by keeping all my ViewPager pages alive using setOffscreenPageLimit(total pages) but have read this is a bad idea since it takes up a large amount of memory.

Here is an example that I use that can help you where an ArrayList of urls for pictures is saved for later when the view is recreated, you need to override onSavedInstanceState and save your variables in a bundle, then retrieve the values from the bundle when the view is created again, hope this can help
ArrayList<String> picutersUrl = new ArrayList<String>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState!=null)
{
picutersUrl = savedInstanceState.getStringArrayList("My Pictures");
}
#Override
protected void onSaveInstanceState(Bundle bundle)
{
super.onSaveInstanceState(bundle);
bundle.putStringArrayList("My Pictures", picutersUrl );
}

I too have used PagerAdapter to support few static tab widgets. I think you need to add code to ScreenSlidePagerAdapter. Google webpage at PagerAdapter.
Note and read the details on override method instantiateItem. This is where you populate the UI.
Code example for ScreenSlidePagerAdapterfrom (PagerAdapter subclass), using your posted code:
#Override
public Object instantiateItem(ViewGroup container, int position) {
View v = inflater.inflate(R.layout.fragment_main, parent, false);
...
trueButton = (Button)v.findViewById(R.id.true_button);
}
In reality with Views in this framework, you're responsible on saving the state of UI elements. In my ListView, I am caching and saving up to 100 rows of data and refreshes it in an Adapter, for example. For caching, I created custom class containing the UI data.

Related

Fragment views in TabLayout not yet created on activity create

I keep running into a recurring issue in many of my apps and have been using all kinds of work arounds to "solve" it, but this time I've had it and I want to figure out a real solution.
I am trying to build a tabbed layout with two tabs where each tab shows some data which should be obtained from the internet. Once the data is obtained it is cached on the device so it can be restored instantly the next time the app is opened (and will then be refreshed in the background).
To this effect I am trying to load the cached data and display it in a RecyclerView in the first tab, and I want to do this on activity create. Before I do this I obviously set up all the tab layout stuff so that the tabs should be properly loaded. The problem is that they are not, it seems the Fragments that make up the tab pages don't have their views yet, hence I cannot access the RecyclerView on them.
Here is my Activity code:
public class MainActivity extends NetworkBusActivity
{
// Views
ViewPager tabPager;
TabLayout tabLayout;
// Tab pager adapter
private ViewPagerAdapter adapter;
// Fragment one and two
private MenuFragment menuFragment;
private OrderFragment orderFragment;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Create the views
tabPager = (ViewPager) findViewById(R.id.tabPager);
tabLayout = (TabLayout) findViewById(R.id.tabLayout);
// Setup the tab layouts
this.setupTabs();
// Show cached data
this.setCachedItems();
// Start loading new data in background
this.startLoading();
}
private void setupTabs()
{
// Create Fragments
menuFragment = new MenuFragment();
orderFragment = new OrderFragment();
// Setup adapter
adapter = new ViewPagerAdapter(getSupportFragmentManager());
adapter.addFragment(menuFragment, getString(R.string.title_menu));
adapter.addFragment(orderFragment, getString(R.string.title_orders));
tabPager.setAdapter(adapter);
// Setup the tab layout
tabLayout.setupWithViewPager(tabPager);
tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
tabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener()
{
// not shown
});
}
private void setCachedItems()
{
// Show cached data
ArrayList<Item> items = Cache.menu.getItems();
menuFragment.setItems(items);
}
private void startLoading()
{
// Start loading in background (not shown)
}
}
It should be straightforward: create the views, create the fragments, and setup the tab layout, then load the cached data.
The MenuFragment extends a base class ItemListFragment which defines the setItems method:
public class MenuFragment extends ItemListFragment
{
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.tab_menu, container, false);
return view;
}
#Override
protected ItemListAdapter getAdapter(ArrayList<Item> items, boolean categorize)
{
return new MenuListAdapter(this, R.layout.row_item, items, categorize);
}
}
public abstract class ItemListFragment extends Fragment
{
private RecyclerView recycler;
#Override
public void onViewCreated(View view, Bundle savedInstanceState)
{
super.onViewCreated(view, savedInstanceState);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity());
recycler = (RecyclerView) view;
recycler.setLayoutManager(linearLayoutManager);
recycler.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL_LIST));
}
public void setItems(ArrayList<Item> items)
{
recycler.setAdapter(getAdapter(items, true));
}
protected abstract ItemListAdapter getAdapter(ArrayList<Item> items, boolean categorize);
}
Again straightforward: create the view in onCreateView, then obtain the RecyclerView in onViewCreated, and finally set the items with an adapter.
The problem is simple: the method setCachedItems in MainActivity is called before the onCreateView or onViewCreated methods are called in the Fragments. Hence, the RecyclerView is null and I can't set its adapter. Even though I am creating a new instance of the Fragments and adding them to a functional TabLayout before I call that method.
There seems to be some delay before the views are created, but I need to set the items already when the activity is created.
Where am I going wrong, and how do I fix it?
In onViewCreated of fragment do this
((MainActivity)getActivity).setCachedData().....
Instead of on create of activity

How to display images in Android and change it by swipe right/left?

I want to add an introduction to my Android application, to inform the user about how the app works. This intro will be displayed only, if the preferred settings intro will be false. So in this intro, there will be 3 images and at the end, there will be a page, with some text and two buttons, to enable the user to access the application, by making a login. The change between each image, will be made with a swipe movement, (so right to left +, left to right -). How Can I do ?
This can be done via the use of Fragments and ViewPager and FragmentPagerAdapter. Look at this documentation:
FragmentPagerAdapter: http://developer.android.com/reference/android/support/v4/app/FragmentPagerAdapter.html
ViewPager:
http://developer.android.com/reference/android/support/v4/view/ViewPager.html
You can have one fragment that is instantiated based on the id in the ViewPager, and that id will indicate which image to show in your image fragment. So for three images, you instantiate a new fragment that sets the image in the fragment based on the current page in the FragmentPagerAdapter. The second fragment can be one for the login buttons and text you want at the end.
Ex for adapter defined in your FragmentActivity (or AppCompatActivity)
public class MyAdapter extends FragmentPagerAdapter {
public MyAdapter(FragmentManager fm) {
super(fm);
}
#Override
public int getCount() {
return NUM_ITEMS;
}
#Override
public Fragment getItem(int position) {
if(position < 3)
return ImageFragment.newInstance(position);
else
return new LoginFragment();
}
}
Ex for the image fragment for the various images in your introduction:
public static class ImageFragment extends Fragment{
private int mPosition;
public ImageFragment(){
}
public static ImageFragment newInstance(int pos){
ImageFragment frag = new ImageFragment();
Bundle args = new Bundle();
args.putInt("pos", pos);
frag.setArguments(args);
return frag;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPosition = getArguments().getInt("pos");
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_image, container, false);
ImageView backgroundView = (ImageView) v.findViewById(R.id.background_image);
switch(mPosition){
case 0:
//set background view image 1
case 1:
//set background view image 2
default:
//set background view image 3
}
return v;
}
}
I would recommend using a ViewPager. Check out this tutorial from the Developer Guide
http://developer.android.com/training/animation/screen-slide.html
If you want to add functionality to each of these pages instead of having just images then perhaps you can implement a fragmentStatePagerAdapter and then put all the functionality in each fragment. Here is a tutorial to implement one.
http://www.truiton.com/2013/05/android-fragmentstatepageradapter-example/
I think we can do it by using recycler view itself.
Using PagerSnapHelper layout manager in recycler view, we can implement swipe to change images.
recyclerView.setLayoutManager(new LinearLayoutManager(this,
LinearLayoutManager.HORIZONTAL, false));
// add pager behavior
PagerSnapHelper snapHelper = new PagerSnapHelper();
snapHelper.attachToRecyclerView(recyclerView);

Saving fragments on rotation when using an Async Task

The question pretty much sums it up, I am trying to save my fragments so when I rotate the screen the application does not crash but I am not sure where or how to save my fragments, I have tried using a fragment manager and setting retainstate to true as well as checking the saved instance state.
This is my code :
EventsActivty - Hosts the fragments
#Override
public void onCreate(Bundle savedInstanceState) {
new AsyncLogin().execute(username, password);
super.onCreate(savedInstanceState);
username = getIntent().getStringExtra("username");
password = getIntent().getStringExtra("password");
}
private List<Fragment> getFragments(){
List<Fragment> fList = new ArrayList<Fragment>();
EventListFragment eventListFragment = (EventListFragment)
EventListFragment.instantiate(this, EventListFragment.class.getName());
EventGridFragment eventGridFragment = (EventGridFragment)
EventGridFragment.instantiate(this, EventGridFragment.class.getName());
fList.add(eventListFragment);
fList.add(eventGridFragment);
return fList;
}
The getFragments is called here, on the OnPostExecute of the AsyncTask
protected void onPostExecute(JSONObject jsonObject) {
try {
getEvents(jsonObject);
setContentView(R.layout.eventlist);
List<Fragment> fragments = getFragments();
pageAdapter = new MyPageAdapter(getSupportFragmentManager(), fragments);
ViewPager pager = (ViewPager)findViewById(R.id.viewpager);
pager.setAdapter(pageAdapter);
}
Fragment 1 : OnCreateView
#Override
public View onCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
eventObjects = ((EventsActivity)getActivity()).getEventObjects();
setRetainInstance(true);
View view = inflater.inflate(R.layout.eventlist ,container,false);
final ListView listView = (ListView) view.findViewById(R.id.listView);
listView.setAdapter(new MyCustomBaseAdapter(getActivity(), eventObjects));
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> a, View v, int position, long id) {
Object o = listView.getItemAtPosition(position);
EventObject fullObject = (EventObject)o;
System.out.println("asd");
}
});
return view;
}
}
Fragment 2:
#Override
public View onCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
setRetainInstance(true);
View view = inflater.inflate(R.layout.eventgrid ,container,false);
GridView gridView = (GridView) view.findViewById(R.id.eventgrid);
gridView.setAdapter(new ImageAdapter(view.getContext())); // uses the view to get the context instead of getActivity().
return view;
}
Actually, a FragmentActivity will automatically restore your fragments in onCreate().
Weird thing is, you call AsyncTask first, prior to calling through to super.onCreate() and retrieving username and password from the Intent.
Even having set that aside, that approach will make your activity spawn a new login task every time it's rotated.
Better way is to check savedInstanceState for null:
if (savedInstanceState == null) {
// first launch
new AsyncLogin().execute(username, password);
}
...
That way it's only going to run when Activity is created for the first time.
Second, you need to completely unbind login info from your activity. Make the AsyncTask return whatever login result you get to the Application and store it there. And your activity and fragments to retrieve that info from the Application - that way you get full control over you login procedure: you check whether there's an active session, if there isn't you check whether the AsyncTask is already running, if it isn't - you need to launch it. If it is - you need to wait for it to finish (show progress bar or something).
You should understand that retaining the fragments won't prevent the activity from being distroyed.
Now take a look here:
#Override
public void onCreate(Bundle savedInstanceState) {
new AsyncLogin().execute(username, password);
// .....
}
When a screen orientation change occurs, your activity is still destroyed. As a result a new AsyncTask is started. But when the old AsyncTask completes the job, it tries to deliver the result to the old activity, that was destroyed already. As a result this will cause a crash.
The solution would be to put the AsyncTask in the fragment itself. So that the instance to not be destroyed on orientation change.
See if this article can help you.

How do I use the same fragment for three tabs with different content?

I have an enum describing three different sports:
public enum MatchType {
S1(0, "Sport1", "xml stream address", R.id.match_list, R.layout.fragment_match_list, R.color.separator_sport1),
S2(0, "Sport2", "xml stream address", R.id.match_list, R.layout.fragment_match_list, R.color.separator_sport2),
S3(0, "Sport3", "xml stream address", R.id.match_list, R.layout.fragment_match_list, R.color.separator_sport3);
...getters/setters
}
I then have fragment with
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
matchesArrayAdapter = new MatchListAdapter(getActivity(), new ArrayList<Match>());
return inflater.inflate(matchType.getLayout(), container, false);
}
Also in my fragment I have an AsyncTask where I have this
#Override
protected void onPostExecute(final List<Match> matches) {
if (matches != null) {
matchListView = (ListView) getActivity().findViewById(matchType.getRId());
[setup listeners]
matchesArrayAdapter.matchArrayList = matches;
matchListView.setAdapter(matchesArrayAdapter);
}
}
EDIT:
In my Activity I have an AppSectionsPagerAdapter with
public Fragment getItem(int i) {
MatchListSectionFragment fragment = new MatchListSectionFragment();
Bundle bundle = new Bundle();
bundle.putInt(Constants.MATCH_TYPE, i);
fragment.setArguments(bundle);
return fragment;
}
EDIT 2:
Here's my onCreate and onCreateView from my fragment:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle bundle = getArguments();
matchType = MatchType.getMatchType(bundle.getInt(Constants.MATCH_TYPE));
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
matchesArrayAdapter = new MatchListAdapter(getActivity(), new ArrayList<Match>());
return inflater.inflate(matchType.getLayout(), container, false);
}
The AsyncTask reads an xml stream for each of the sports in my enum but my problem is that tab #1 is overwritten with data from tab #2 and subsequently tab #3.
Before I had a fragment defined for each sport but surely that can't be necessary?
How do I go about using the same fragment with the same layout for each sport?
When instantiating your fragment in your Activity set the Fragment's arguments with a Bundle.
Bundle myBundle = new Bundle();
myBundle.putInt(MY_EXTRA, 1);
myFragment.setArguments(myBundle);
In your bundle put some Extra that will be read in the fragment's onCreate() callback.
int i = getArguments().getInt(MY_EXTRA);
I am on mobile.
Put three FrameLayouts in a LinearLayout, named frame1, frame2 and frame 3. This will be your main Activity's layout.
Then in the Activity's oncreate() method, call getFragmentManager().getFragmentTransaction().
Instantiate the three fragments and send them the data, preferably through a Bundle.
On the Fragment Transaction call the add() or replace() method for each fragment, the first parameter is the id of the respective FrameLayout, the second parameter is the fragment itself.
Call commit().
You should create the newInstance method in your fragment, also you should store MatchType instansce in you fragment.
MatchType matchType;
public static MyFragment newInstance(MatchType matchType) {
MyFragment fragment = new MyFragment();
fragment.matchType = matchType;
return fragment;
}
In your Activity you should to create 3 instances of MyFragment with this method (with related to each fragment it owns MatchType). Then in onCreateView method you should insert data to your views from matchType.
Sorry, I'm on mobile. And sorry for my English.
Update
Check your variable matchType. Maybe it declared as static?

Get position of ViewPager after rotating in Android

I have a ViewPager. My FragmentPageAdapter returns the position of the Viewpager in the getItem() methode. But after rotating the Screen the methode returns no value. Why? If I understood it right, everytime you rotate the screen OnCreateView() is called, but why doesn't it return the value any more? Could someone point out how to solve this? Thank you
Edit: My FragmentPageAdapter:
public Fragment getItem(int position) {
return Fragment_results.newInstance(position);
}
My Fragment:
public static Fragment_results newInstance(int i) {
Fragment_results fragment = new Fragment_results();
fragment.mContent = i +"";
return fragment;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.layout_result, null);
((TextView) view.findViewById(R.id.text)).setText(mContent);
The position is set to 0 upon creating of the ViewPager instance and whenever you set a new adapter. When onCreateView() is called, you're rebuilding the entire app essentially. In order to revert back to the position it was, you must first use onSavedInstanceState(Bundle savedInstanceState) and store the item position via the Bundle.
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("pageItem", myViewPager.getCurrentItem());
}
Then in onCreate, restore the viewPager's state like so:
if (savedInstanceState != null) {
myViewPager.setCurrentItem(savedInstanceState.getInt("pageItem", 0));
}

Categories

Resources