Swiping through unlimited fragments - Confused over ViewPager process - android

I am trying to create an app that pulls these Ron Swanson quotes from an api and displays them in a fragment. When the fragment is swiped, the app will go to another fragment and display a new quote. This process is supposed to repeat indefinitely.
Instead, the app loads to a blank screen. I have placed a button and TextView on the fragment, each with initial texts to determine if it has loads, but nothing ever appears.
P.S.: you may also notice some comical variable names and comments. I obviously didn't consider that I may need help when writing it (it's just a personal app for learning).
P.P.S.: I'm honestly not certain the site is even still up, but that shouldn't affect anything at this point.
Thanks to anyone who can help. Below is the 1) single java file of the app 2) activity_main.xml file.
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
public class MainActivity extends FragmentActivity {
#SuppressWarnings("deprecation")
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//
ViewPager pagingRon = (ViewPager) findViewById(R.id.pager);
PagerAdapter RonsPagerAdapter = new CustomPagerAdapter(getSupportFragmentManager());
pagingRon.setAdapter(RonsPagerAdapter);
pagingRon.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrollStateChanged(int arg0) {
// TOxDO Auto-generated method stub
}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
// TODO Auto-generated method stub
}
#Override
public void onPageSelected(int arg0) {
// TODO Auto-generated method stub
}
});
//Initial AsyncTask for setting up first quote of the Great Swan
RonSwanSetup ronSwansonOG = new RonSwanSetup();
ronSwansonOG.execute();
}
private class RonSwanSetup extends AsyncTask<Void, String, Void> {
#Override
protected Void doInBackground(Void... Socialism) {
String Ronquoteson = ""; //Ron's wonderful first quote
String Rondotcom = "http://ron-swanson-quotes.herokuapp.com/quotes"; //the website for fetching Ron's wonderful quotes
String[] Ronsquotes = new String[1]; //Ron only speaks seldomly, which is the pinnacle of wisdom
try {
URL Ronsurl = new URL(Rondotcom);
HttpURLConnection Ronsconnection = (HttpURLConnection) Ronsurl.openConnection();
BufferedReader thanksRon = new BufferedReader(new InputStreamReader(Ronsconnection.getInputStream()));
String current;
while((current = thanksRon.readLine()) != null)
{
Ronquoteson += current;
}
Ronsquotes[0] = Ronquoteson;
} catch (Exception sorryRon) {
Ronsquotes[0] = "The app appears unable to do even the SIMPLEST THINGS CORRECTLY - Maybe try restarting it?";
sorryRon.printStackTrace();
}
publishProgress(Ronsquotes);
return null;
}
protected void onProgressUpdate(String... Ronsquote) {
TextView RonsText = (TextView) findViewById(R.id.text);
String RonQouteson = Ronsquote[0];
RonsText.setText("Not Ron's Quote");//For testing
}
}
public class CustomPagerAdapter extends FragmentPagerAdapter {
public CustomPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
// This method returns the fragment associated with
// the specified position.
// It is called when the Adapter needs a fragment
// and it does not exist.
public Fragment getItem(int position) {
// Create fragment object
RonFragSon fragment = new RonFragSon();
// Attach some data to it that we'll
// use to populate our fragment layouts
Bundle args = new Bundle();
args.putInt("page_position", position + 1);
// Set the arguments on the fragment
// that will be fetched
fragment.setArguments(args);
return fragment;
}
#Override
public int getCount() {
return 3;
}
}
public class RonFragSon extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout resource that'll be returned
View rootView = inflater.inflate(R.layout.swansons_fragment, container, false);
// Get the arguments that was supplied when
// the fragment was instantiated in the
// CustomPagerAdapter
Bundle args = getArguments();
((TextView) rootView.findViewById(R.id.text)).setText("Page");
return rootView;
}
}
}
<HorizontalScrollView
android:id="#+id/horizontal_Swangrid"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="40"
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- I don't think this is necessary, but don't want to get rid of it in case I'm wrong
<LinearLayout
android:id="#+id/Swanson_cant_container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal" >
<TextView
android:id="#+id/RonSwan1stQuote"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="#+id/RonSwan2ndQuote"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="#+id/RonSwan3rdQuote"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
-->
<android.support.v4.view.ViewPager
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</HorizontalScrollView>

Related

Does FragmentStatePagerAdapter save fragment state on orientation change?

I have 3 fragments that need to be in a ViewPager. These fragment will hold dynamic information retrieved from a database. I understand that on an orientation change, the activity and fragments are destroyed and recreated. But I was under the impression by its name, that the FragmentStatePagerAdapter will save the state of the fragment. Apparently, I was wrong because every time I did something to the fragment, then change orientation, the fragment is reverted back to how it was laid out in the layout xml file.
As I was debugging, I noticed that on orientation change, the Adapter's getItem() method was never invoked - meaning that it wasn't recreated. So then how come the fragment state reverted back to its original state?
How do I save the fragment state using the FragmentStatePagerAdapter?
Please note that I have been following this tutorial and used their version of the SmartFragmentStatePagerAdapter.java class to manage the fragment dynamically.
And the following are my sample codes.
PageLoader.java - This interface allows MainActivity to manage the loading of the fragment pages dynamically at run time.
public interface PageLoader {
void loadPage(int from, int target);
}
MainActivity.java
public class MainActivity extends AppCompatActivity implements PageLoader {
MyPagerAdapter adapter;
DirectionalViewPager pager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Get the ViewPager and set it's PagerAdapter so that it can display items
pager = (DirectionalViewPager) findViewById(R.id.vpPager);
adapter = new MyPagerAdapter(getSupportFragmentManager());
pager.setOffscreenPageLimit(5);
pager.setAdapter(adapter);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int position = pager.getCurrentItem();
if (position > 0) pager.setCurrentItem(position - 1);
return true;
}
#Override
public void loadPage(int from, int target) {
PageLoader fragment = (PageLoader) adapter.getRegisteredFragment(target);
fragment.loadPage(from, target);
}
}
MyPagerAdapter.java
public class MyPagerAdapter extends SmartFragmentStatePagerAdapter {
private static final int NUM_ITEMS = 4;
public MyPagerAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
}
// Returns total number of pages
#Override
public int getCount() {
return NUM_ITEMS;
}
// Returns the fragment to display for that page
#Override
public Fragment getItem(int position) {
switch (position) {
case 0: // Fragment # 0 - This will show Frag1
return Frag1.newInstance(position, Frag1.class.getSimpleName());
case 1: // Fragment # 0 - This will show Frag1 different title
return Frag1.newInstance(position, Frag1.class.getSimpleName());
case 2: // Fragment # 1 - This will show Frag2
return Frag2.newInstance(position, Frag2.class.getSimpleName());
default:
return Frag3.newInstance(position, Frag3.class.getSimpleName());
}
}
// Returns the page title for the top indicator
#Override
public CharSequence getPageTitle(int position) {
return "Page " + position;
}
}
Frag1.java Frag2.java Frag3.java - these are all the same, except for the numbering.
public class Frag1 extends Fragment implements PageLoader {
// Store instance variables
private String title;
private int page;
private TextView txtView;
// newInstance constructor for creating fragment with arguments
public static Frag1 newInstance(int page, String title) {
Frag1 fragmentFirst = new Frag1();
Bundle args = new Bundle();
args.putInt("someInt", page);
args.putString("someTitle", title);
fragmentFirst.setArguments(args);
return fragmentFirst;
}
// Store instance variables based on arguments passed
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
page = getArguments().getInt("someInt", 0);
title = getArguments().getString("someTitle");
}
// Inflate the view for the fragment based on layout XML
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.frag1, container, false);
txtView = (TextView) view.findViewById(R.id.txt_frag1);
txtView.setText(page + " - " + title);
Button btn = (Button) view.findViewById(R.id.btn_frag1);
btn.setOnClickListener( new View.OnClickListener() {
#Override
public void onClick(View v) {
PageLoader activity = (PageLoader) getActivity();
activity.loadPage(page, page+1);
}
});
return view;
}
#Override
public void loadPage(int from, int target) {
txtView.setText(txtView.getText() + "\nThis message was created from" + from + " to " + target);
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.example.someone.smartfragmentstatepageradapter.custom.DirectionalViewPager
android:id="#+id/vpPager"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</com.example.someone.smartfragmentstatepageradapter.custom.DirectionalViewPager>
</LinearLayout>
frag1.xml frag2.xml frag3.xml - again these are all the same except for the numbering
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#cc2">
<TextView
android:id="#+id/txt_frag1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="txt_frag1"
/>
<Button
android:id="#+id/btn_frag1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="btn_frag1"
android:textSize="26dp" />
</LinearLayout>
PLEASE tell me how I can use the FragmentStatePagerAdapter to save the "State" of my fragments. I've been scouring the internet from 9am to 9pm today... 12 hours... I really need some help figuring this out. Thanks in advance!
EDIT Try this:
Add another instance variable to your fragment:
private String text; // this is part of saved state
Set this variable in loadPage:
#Override
public void loadPage(int from, int target) {
text = txtView.getText().toString() + "\nThis message was created from" + from + " to " + target;
txtView.setText(text);
}
Override onSaveInstanceState to save this variable:
#Override
public void onSaveInstanceState(Bundle outState);
outState.putString("text", text);
super.onSaveInstanceState(outState);
}
Then restore the the TextView state using this variable:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (savedInstanceState != null) {
// not null means we are restoring the fragment
text = savedInstanceState.getString("text");
} else {
text = "" + page + " - " + title;
}
View view = inflater.inflate(R.layout.frag1, container, false);
txtView = (TextView) view.findViewById(R.id.txt_frag1);
txtView.setText(text);
Button btn = (Button) view.findViewById(R.id.btn_frag1);
btn.setOnClickListener( new View.OnClickListener() {
#Override
public void onClick(View v) {
PageLoader activity = (PageLoader) getActivity();
activity.loadPage(page, page+1);
}
});
return view;
}
Any time you want something in your fragment to stay the same when things like configuration changes occur, this is how you would track the state, then save and restore it.
This is where you would use onSaveInstanceState for fragments and activities.
This is the method you would override to save any necessary state. Anything you change in your fragment that you want to have recreated on configuration change must be saved and then restored during onCreate or onCreateView.
So if you're trying to restore the text created in loadPage, you would create a class-level String for the text, set it in loadPage, save that in the onSaveInstanceState override, and then restore in in onCreateView from the savedInstanceState parameter.
Now here's the kicker: You are noticing that getItem on your adapter isn't called after a config change. But did you notice that your fragment is still there (even though it wasn't how you left it)? Keep in mind that the activity has a FragmentManager that is managing the fragments and their transactions. When the activity goes to config change, it saves its state. The FragmentManager and all of the active fragments are part of that state. Then the fragments are restored in such a way that adapter.getItem isn't called.
Turns out, that SmartFragmentPagerAdapter isn't so smart. It can't recreate its registeredFragments array after a configuration change, so it's really not very useful. I would discourage you from using it.
So how do you send events to off-page fragments when the ViewPager has appropriated the fragment's tag for its own use?
The technique I use is to define event listener interfaces, and have the fragments register as listeners with the activity. When I fire an event, it's by calling a method on the activity that notifies its active listeners. I give a pretty complete example of this in this answer.

Go to next page in ViewPager

I have a ViewPager in my MainActivity. Every page in it is a Fragment. So, everytime I swipe right or left, a new instance of the fragment is created and the fragment view is updated accordingly.
I also have two buttons: LEFT and RIGHT for navigation. These buttons are inside the Fragment, not in the Activity. A user can either swipe or alternatively press the relevant button to navigate between the pages.
Here's the problem: Since I'm changing the views through my MainActivity, how do I detect the onClick events of those buttons in the Activity in order to update the fragment?
Here's the PagerAdapter class (Removed all the irrelevant code from everywhere):
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
// logic part for the views.
return PlaceholderFragment.newInstance(sectionNumber, questionStatus, questionOrder, showNext, showPrevious);
}
And here's the PlaceHolderFragment class:
public class PlaceholderFragment extends Fragment{
//some global variables
public static PlaceholderFragment newInstance(int sectionNumber, int questionStatus, String questionOrder, boolean showNext, boolean showPrevious) {
PlaceholderFragment fragment = new PlaceholderFragment();
//setting up the arguments
fragment.setArguments(args);
return fragment;
}
public PlaceholderFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.explanation_fragment, container, false);
//code for filling up all the views
RightButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//can I do something here?
}
});
return rootView;
}
}
MORE INFO:
I have to keep the navigation buttons in the fragment itself and not in the activity.
In your fragment write one interface like:
public class PlaceholderFragment extends Fragment{
private OnButtonClickListener mOnButtonClickListener;
interface OnButtonClickListener{
void onButtonClicked(View view);
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
try {
mOnButtonClickListener = (OnButtonClickListener) context;
} catch (ClassCastException e) {
throw new ClassCastException(((Activity) context).getLocalClassName()
+ " must implement OnButtonClickListener");
}
}
yourButtons.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mOnButtonClickListener.onButtonClicked(v);
}
});
}
And in your mainactivity:
class MainActivity extends AppCompatActivity implements OnButtonClickListener{
#Override
void onButtonClicked(View view){
int currPos=yourPager.getCurrentItem();
switch(view.getId()){
case R.id.leftNavigation:
//handle currPos is zero
yourPager.setCurrentItem(currPos-1);
break;
case R.id.rightNavigation:
//handle currPos is reached last item
yourPager.setCurrentItem(currPos+1);
break;
}
}
}
For Kotlin and ViewPager2
previous page:
val currPos: Int = xyzViewPager.currentItem
if (currPos != 0) {
xyzViewPager.currentItem = currPos - 1
}
next page:
val currPos: Int = xyzViewPager.currentItem
if ((currPos + 1) != xyzViewPager.adapter?.count) {
xyzViewPager.currentItem = currPos + 1
}
Use kotlin extension functions:
fun ViewPager2.nextPage(smoothScroll: Boolean = true): Boolean {
if ((currentItem + 1) < adapter?.itemCount ?: 0) {
setCurrentItem(currentItem + 1, smoothScroll)
return true
}
//can't move to next page, maybe current page is last or adapter not set.
return false
}
fun ViewPager2.previousPage(smoothScroll: Boolean = true): Boolean {
if ((currentItem - 1) >= 0) {
setCurrentItem(currentItem - 1, smoothScroll)
return true
}
//can't move to previous page, maybe current page is first or adapter not set.
return false
}
Make a public method in your activity
public void swipeRight(int x){
if(x < totalNumberOfFragment){
viewPager.setCurrentItem(x + 1);
}
}
public void swipeLeft(int x){
if(x > 0){
viewPager.setCurrentItem(x - 1);
}
}
You can call these method from your fragment button's click action
RightButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//Yes
((YourActivityClassName)getActivity()).swipeRight(position);
}
});
And do same for LeftButton
YourActivityClassName - Activity which is holding this viewPager fragment.
position - position of your current fragment.
You can add button in your activities xml as follows
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1" >
<android.support.v4.view.ViewPager
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</android.support.v4.view.ViewPager>
</LinearLayout>
<ImageView
android:id="#+id/leftNavigation"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:padding="10dp"
android:src="#drawable/ic_chevron_left_black_24dp"
android:visibility="gone" />
<ImageView
android:id="#+id/rightNavigation"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="right"
android:layout_weight="0"
android:gravity="center"
android:padding="10dp"
android:src="#drawable/ic_chevron_right_black_24dp" />
</FrameLayout>
You can create special method in your parent Activity like following:
public class PagerActiviy extends Activity {
...
public void onFragmentNavigationButtonClick(View button) {
// Do your stuff here
}
...
}
Then in your fragments you can get your parent Activity with getActivity() method, cast it to the actual type and call the method:
public class PlaceholderFragment extends Fragment{
...
...
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.explanation_fragment, container, false);
//code for filling up all the views
RightButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
((PagerActvity) getActivity()).onFragmentNavigationButtonClick(v);
}
});
return rootView;
}
}
As a side note I should add that from your example it's not clear why do you need to keep your navigation buttons in the fragments and can't move them to the Activity and manage them there.
You can send a broadcast upon button click inside the fragment like below:
Intent intent = new Intent();
intent.setAction(NUMBER_OF_RECEIVER_NEXT);
getActivity().sendBroadcast(intent);
Then in your main activity you catch the Intent and you set the current position of the pager to the desired number
private class broadcastReceived extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
String actionGet = intent.getAction();
if(actionGet.equals(NUMBER_OF_RECEIVER_NEXT)){
mPager.setCurrentItem(1);
Log.e("IntentNextPage","Received");
}
}
}
Do not forget to set a
private static final String NUMBER_OF_RECEIVER_NEXT = "nextPage";
in both main activity and fragment and of course to register,unregister receiver to onResume and onPause methods.
Also add an intent filter to the receiver in onCreate methd with the action specified as NUMBER_OF_RECEIVER_NEXT

Same Fragments in FragmentPageAdapter

I am developing an ExpenseManager app in android, where I am implementing a fragmentActivity. This fragment shows the account summary page with details such as Account Balance, Account Last Transaction Amount and other details.
The AccountSummary Activity is a multi tab activity implemented with ViewPager, ActionBar, FragmentPageAdapter and FragmentActivity.
The layout of summary page will be common for all the accounts and only the data will get changed depending which account user has selected.
Now, I want to implement this activity where I can reuse/ duplicate the same fragment layout (not the instance) across all the accounts or (ActionBar Tabs). When the user selects any tab from actionBar, it should load/show/display a fragment along with that account data.
(I understand that I need to create dynamic Fragment with different TagName and need to replace with the help of FragmentTransaction; for some reason, this solution doesn't work).
This is similar to the application on playstore with the App layout as Url
Could someone post a solution by using an example code ? I've been struggling to find a solution, but nothing seems to work.
EDIT
AccountSummaryTabActivity.java
public class AccountSummaryTabActivity extends FragmentActivity implements ActionBar.TabListener {
ViewPager viewPager = null;
ActionBar actionBar = null;
AccountSummaryTabAdapter accSummaryAdapter = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_account_summary_tab);
DBHelper dbHelper = new DBHelper(getApplicationContext());
RuntimeExceptionDao<Account, Integer> simpleAccountDao = dbHelper.getSimpleAccountDataDao();
List<Account> accountList = simpleAccountDao.queryForAll();
viewPager = (ViewPager)findViewById(R.id.accountSummaryPager);
actionBar = getActionBar();
accSummaryAdapter = new AccountSummaryTabAdapter(getSupportFragmentManager());
accSummaryAdapter.setCount(accountList.size());
viewPager.setAdapter(accSummaryAdapter);
actionBar.setHomeButtonEnabled(false);
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
String[] accountNameArr = new String[accountList.size()];
Integer i=1;
for (Account account : accountList) {
if(account.getIsPrimaryAcc().equalsIgnoreCase("Y"))
accountNameArr[0] = account.getName();
else
accountNameArr[i++] = account.getName();
}
// Adding Tabs
for (String accountName : accountNameArr) {
actionBar.addTab(actionBar.newTab().setText(accountName).setTabListener(this));
}
Intent intent = getIntent();
if(intent.getStringExtra("selectedAccNameTab")==null)
intent.putExtra("selectedAccNameTab", accountNameArr[0]);
viewPager.setOnPageChangeListener(new OnPageChangeListener() {
#Override
public void onPageSelected(int arg0) {
actionBar.setSelectedNavigationItem(arg0);
}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
// TODO Auto-generated method stub
}
#Override
public void onPageScrollStateChanged(int arg0) {
// TODO Auto-generated method stub
}
});
}
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
System.out.println("onTabSelected "+tab.getText());
DBHelper dbHelper = new DBHelper(getApplicationContext());
RuntimeExceptionDao<Account, Integer> simpleAccountDao = dbHelper.getSimpleAccountDataDao();
List<Account> accountList = simpleAccountDao.queryForAll();
Intent intent = getIntent();
intent.putExtra("selectedAccNameTab", tab.getText());
accSummaryAdapter = new AccountSummaryTabAdapter(getSupportFragmentManager());
accSummaryAdapter.setCount(accountList.size());
viewPager.setAdapter(accSummaryAdapter);
}
#Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
System.out.println("onTabUnselected "+tab.getText());
}
#Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
System.out.println("onTabReselected "+tab.getText());
}
}
AccountSummaryTabAdapter.java
public class AccountSummaryTabAdapter extends FragmentPagerAdapter {
FragmentManager fm=null;
Integer count = 0;
public AccountSummaryTabAdapter(FragmentManager fm) {
super(fm);
this.fm=fm;
}
public void setCount(Integer count){
this.count=count;
}
#Override
public Fragment getItem(int arg0) {
switch (arg0){
default :{
AccountSummaryFragment fragment = new AccountSummaryFragment();
return fragment;
}
}
}
#Override
public int getCount() {
return count;
}
}
AccountSummaryFragment.java
public class AccountSummaryFragment extends Fragment {
public AccountSummaryFragment() {
super();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.activity_account_summary, container, false);
String accountName = getActivity().getIntent().getStringExtra("selectedAccNameTab");
DBHelper dbHelper = new DBHelper(getActivity());
RuntimeExceptionDao<Account, Integer> simpleAccountDao = dbHelper.getSimpleAccountDataDao();
List<Account> accountList = simpleAccountDao.queryForEq("name", accountName);
Account defaultAcc = accountList.get(0);
String[] accountNameArr = new String[]{"Monthly","Quaterly","Half Yearly","Yearly"};
TextView balanceTxVw = (TextView) rootView.findViewById(R.id.accountBalanceTxVw);
Double balance = defaultAcc.getBalance();
DecimalFormat df = new DecimalFormat("0.00");
balanceTxVw.setText(df.format(balance));
Spinner dropdown = (Spinner)rootView.findViewById(R.id.graphSpinner);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_spinner_item, accountNameArr);
dropdown.setAdapter(adapter);
return rootView;
}
}
activity_account_summary_tab.xml Layout file
<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/accountSummaryPager"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v4.view.ViewPager>
Thanks in advance
The most likely way to solve this problem is to do the following:
Since you have the same layout for most of the accounts;
You can indeed use the same fragment but then you should keep track of which tab is selected.
That tab (int) will be passed back to the TabsPagerAdapter. Next;
Based on that integer value, you should return the same fragment but with a different Extra value - for instance say tab 2.
Inside onAttach of your fragment, you can getArguments() or extras and switch on it to get the correct data for that particular fragment.
After using that particular integer to get the correct values, you can assign values to the class variables - that implies that you should make them class-level variables.
I hope that helps a bit. Let me know how it goes.

How to use mapfragment in a fragment

Mainly i got a FragmentActivity with a drop down menu that once you choose an item from that menu it makes my private class that extends Fragment to show every time other things on the map it self
Now the problem is that when i make the line inside the fragment class that controls the map :
GoogleMap map = ((MapFragment)getFragmentManager.findFragmentById(R.id.map)).getMap()
It says you cant cast fragment to MapFragment so i tried to extend the MapFragment for the class but when i choose an item inside the dropdown menu i do this section of the code :
Fragment fragment = new MyFragmentClass()();
Bundle args = new Bundle();
args.putInt(PlacesFinder.ARG_SECTION_NUMBER, position + 1);
fragment.setArguments(args);
getSupportFragmentManager().beginTransaction().replace(R.id.container, fragment).commit();
i can only use fragment so how can i use the google map inside the fragment if i cant findview it inside the fragment class and cant extends mapfragment because of the dropdown menu?
Thanks head up :)
You can use a GoogleMap inside a fragment. Looking at just the part of your code that creates the fragment in XML there are errors. Assuming this fragment is the only element in your XML layout this is how it should read:
<fragment xmlns:android="schemas.android.com/apk/res/android"
android:id="#+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent"
class="com.google.android.gms.maps.MapFragment"/>
The last line of your XML, where you have android:name is wrong. You code adding the map within the activity should work
GoogleMap map = ((MapFragment)getFragmentManager.findFragmentById(R.id.map)).getMap();
I have used this code below with no problems before
mapFrag = ((SupportMapFragment) getFragmenManager().findFragmenById(R.id.map)).getMap();
The only difference is (1) I was using a SupportMapFragment; (2) I declared my GoogleMap outside of onCreate.
It sounds to be based on what you're describing with the fragment not being able to be cast to the right class that this is an issue either with the error in the fragment XML or you are not using the right imports. If you are using MapFragment then you need make sure you're using the MapFragment import (not SupportMapFragment or just Fragment).
As to the last part of you're code I'm not sure what the relationship with the dropdown menu is. It looks like you're passing arguments to a fragment? If you can provide more details I will comment on that as well.
Sorry for the delay but This is the main activity:
public class MainActivity extends FragmentActivity implements
ActionBar.OnNavigationListener , LocationListener{
private String [] types;
private ActionBar actionBar;
public static GoogleMap map = null;
public static boolean googlePlayOn = false;
private static final String STATE_SELECTED_NAVIGATION_ITEM = "selected_navigation_item";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
types = getResources().getStringArray(R.array.values);
googlePlayOn = isGooglePlay();
actionBar = getActionBar();
actionBar.setDisplayShowTitleEnabled(false);
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
// Set up the dropdown list navigation in the action bar.
actionBar.setListNavigationCallbacks(
// Specify a SpinnerAdapter to populate the dropdown list.
new ArrayAdapter<String>(getActionBarThemedContextCompat(),android.R.layout.simple_list_item_1,android.R.id.text1, types), this);
}
/**
* This function checks in the phone operating system that
* the phone got google play services
* #return
*/
private boolean isGooglePlay()
{
int status = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
if(status == ConnectionResult.SUCCESS)
return true;
else
((Dialog) GooglePlayServicesUtil.getErrorDialog(status, this, 10)).show();
return false;
}
#TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
private Context getActionBarThemedContextCompat() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
return getActionBar().getThemedContext();
} else {
return this;
}
}
#Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
// Restore the previously serialized current dropdown position.
if (savedInstanceState.containsKey(STATE_SELECTED_NAVIGATION_ITEM)) {
getActionBar().setSelectedNavigationItem(
savedInstanceState.getInt(STATE_SELECTED_NAVIGATION_ITEM));
}
}
#Override
public void onSaveInstanceState(Bundle outState) {
// Serialize the current dropdown position.
outState.putInt(STATE_SELECTED_NAVIGATION_ITEM, getActionBar()
.getSelectedNavigationIndex());
}
#Override
public boolean onNavigationItemSelected(int position, long id) {
// When the given dropdown item is selected, show its contents in the
// container view.
if(position != PlacesFinder.currentTitle)
{
if(!PlacesFinder.created)
{
PlacesFinder.created = true;
Bundle b = new Bundle();
b.putInt(PlacesFinder.ARG_SECTION_NUMBER, position);
Fragment fragment = new PlacesFinder();
fragment.setArguments(b);
getSupportFragmentManager().beginTransaction().replace(R.id.container, fragment).commit();
}
PlacesFinder.newTitleBeenChosenHandler.sendEmptyMessage(0);
}
return true;
}
/**
* A dummy fragment representing a section of the app, but that simply
* displays dummy text.
*/
public static class PlacesFinder extends Fragment {
/**
* The fragment argument representing the section number for this
* fragment.
*/
public static boolean created = false;
public static final String ARG_SECTION_NUMBER = "section_number";
public static Handler newTitleBeenChosenHandler;
public static int currentTitle = 0;
private Context context;
public PlacesFinder() {
this.context = getActivity();
loadMapFragment();
}
private void loadMapFragment()
{
if(googlePlayOn)
{
if(map == null)
map = ((SupportMapFragment) getFragmentManager().findFragmentById(R.id.map)).getMap();
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.places_fragment, container , false);
}
}
#Override
public void onProviderDisabled(String provider) {
// TODO Auto-generated method stub
}
#Override
public void onProviderEnabled(String provider) {
// TODO Auto-generated method stub
}
#Override
public void onStatusChanged(String provider, int status, Bundle extras) {
// TODO Auto-generated method stub
}
#Override
public void onLocationChanged(Location location) {
// TODO Auto-generated method stub
}
}
This is the main xml file :
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
tools:ignore="MergeRootFrame" />
This is the mapfragment file :
<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.google.android.gms.maps.MapFragment"/>
Once i do the line of code
map = ((SupportMapFragment) getFragmentManager().findFragmentById(R.id.map)).getMap();
the apps fails and it says i got a null pointer and i dont have a clue why...
You should use getSupportFragmentManager().
map = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map)).getMap();
As you're using FragmentActivity, this should work. I'm facing the same problem although I'm using Fragment and that won't do it :(

Why do the text of EditText, Button etc reset when the view is hidden by another layout? How can I make them retain the text?

Basically
I have two layouts activity_main_layout.xml as well as layout2.xml
I have created two tabs on the ActionBar using the getActionbar().newTab() method
I have a Button and a text box (EditText) in activity_main_layout.xml (first tab)
Both the button and the text box have a dynamically entered text: "Test 123"
When Tab 2 is selected, I have a statement of code that says: setContentView(R.layout.layout2) so that it shows the second layout and similarly for Tab 1: setContentView(R.layout.activity_main)
When Tab 1 is reselected, the dynamically entered text "Test 123" is gone and I am left with the text that was set in design time in the XML. So I am currently forced the save the contents of the Buttons and EditTexts before a tab is selected and restore them after it was selected.
I don't think this is the right way to do it. I am already utilizing onSaveInstanceState() and onRestoreInstanceState() with savedInstanceState.putXX and getXX etc to save the contents when the screen rotates but it doesn't seem to work with the current issue. I have been unable to find an answer so far. Does anyone have any clues please? Any responses will be appreciated.
If you have only 2 tabs, why don't you use a pageadapter? You won't lose any information until you change the position of the mobile or quit the app.
Basically is something like that (I put you a scracth of one of my codes).
NOTE: I use packet ShelockActivity to have tabsmenu for android > 2.0
MAINCLASS:
public class MainClass extends SherlockActivity implements ActionBar.TabListener {
ViewPager myPager;
AdapterClass adapter;
Context con = null;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
con = this.getBaseContext();
//Restore info in case flip mobile
final Myobject data = (Myobject) getLastNonConfigurationInstance();
Bundle extras = getIntent().getExtras();
//Tab bar
getSupportActionBar().setDisplayShowHomeEnabled(false);
getSupportActionBar().setDisplayShowTitleEnabled(false);
getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
//loop for add more
ActionBar.Tab tab = getSupportActionBar().newTab();
tab.setText("text tab");
if (data != null) {
getdatatoyourobject(data);
}
myPager = (ViewPager) findViewById(R.id.panelpager);
myPager.setAdapter(adapter);
myPager.setOnPageChangeListener(new OnPageChangeListener() {
#Override
public void onPageSelected(int index) {
getSupportActionBar().setSelectedNavigationItem(index);
}
adapter = new AdapterClass(Parameters for your adapterclass);
#Override
public void onPageScrollStateChanged(int arg0) {
// TODO Auto-generated method stub
}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
// TODO Auto-generated method stub
}
});
}
}
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
// TODO Auto-generated method stub
myPager.setCurrentItem(tab.getPosition(),true);
}
#Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
// TODO Auto-generated method stub
}
#Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
// TODO Auto-generated method stub
}
#Override
public Object onRetainNonConfigurationInstance() {
final Myobject a = new Myobject();
return a;
}
public class Myobject {
Type variable = adapter.variable; //do for all variables to restore
}
public void getdatatoyourobject(Myobject a){
adapter.variable = a.variable; //do for all variables to restore
}
}
ADAPTERCLASS:
public class Adapterclass extends PagerAdapter {
public int getCount() {
return 2; //NUMBER OF PAGES
}
public Adapterclass () {
}
public View view = null; //create rest of variables to restore as public
public Object instantiateItem(View collection, int position) {
LayoutInflater inflater = (LayoutInflater) collection.getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
switch (position) {
case 0:
view = inflater.inflate(R.layout.page1, null);
//bla bla bla
break;
//do more cases (pages)
}
((ViewPager) collection).addView(view, 0);
return view;
}
#Override
public void destroyItem(View arg0, int arg1, Object arg2) {
((ViewPager) arg0).removeView((View) arg2);
}
#Override
public boolean isViewFromObject(View arg0, Object arg1) {
return arg0 == ((View) arg1);
}
#Override
public Parcelable saveState() {
return null;
}
}
MAIN.XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<android.support.v4.view.ViewPager
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/panelpager"/>
here is the code I have:
activity_main.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<Button
android:id="#+id/btn_One"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_weight="1.70"
android:text="Activity_main.xml - Design Time Text"
android:textSize="12sp"
android:onClick="btnOne_OnClick"
android:textStyle="normal" />
layout2.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<Button
android:id="#+id/btn_Two"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Layout2.xml - Design Time Text"
android:textSize="12sp"
android:textStyle="normal" />
In the main code, pay attention to the comments please:
public class MainActivity extends Activity implements ActionBar.TabListener {
ActionBar.Tab Tab_1 = null, Tab_2 = null;
Button btn_One = null, btnTwo = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
///////////////////////////////////////////////////////////////////
ActionBar actionbar = (ActionBar) getActionBar();
actionbar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
Tab_1 = actionbar.newTab().setText("Tab 1");
Tab_2 = actionbar.newTab().setText("Tab 2");
Tab_1.setTag(1);
Tab_2.setTag(2);
Tab_1.setTabListener(this);
Tab_2.setTabListener(this);
actionbar.addTab(Tab_1);
actionbar.addTab(Tab_2);
///////////////////////////////////////////////////////////////////
btn_One = (Button)this.findViewById(R.id.btn_One);
}
public void btnOne_OnClick(View v) {
((Button)v).setText("Clicked");
btn_One = ((Button)v);
}
#Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {}
// Here is what the problem is:
// After you click btn_one, the text changes to "Clicked" - good! - however
// after Tab 1 gets reselected, the text off btn_One (btn_One) gets reset back to the one
// set in design time: "Activity_main.xml - Design Time Text", so the "Clicked" text disappears
// The interesting thing is, when you rotate the screen, the text gets re-initialized to "Clicked" and that's
// because of onRestoreInstanceState(). So it looks like when a tab gets reselected, the design time screen
// gets re-drawn and onRestoreInstanceState doesn't get recalled.
// My "workaround" for the above issue would be to create a global
// "Bundle" poiner and pass the address of the one from onSaveInstanceState(Bundle savedInstanceState)
// to the global Bundle pointer and then call onRestoreInstanceState(*the_global_bundle_pointer*)
// when a tab gets reselected so that it restores the state.
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
int selectedTab = (Integer) tab.getTag();
if (selectedTab == 1) this.setContentView(R.layout.activity_main);
if (selectedTab == 2) this.setContentView(R.layout.layout2);
}
#Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {}
#Override
public void onSaveInstanceState(Bundle savedInstanceState)
{
if (btn_One != null) savedInstanceState.putString("btn_One", btn_One.getText().toString());
super.onSaveInstanceState(savedInstanceState);
}
// When the instance is restored, reload all data
#Override
public void onRestoreInstanceState(Bundle savedInstanceState)
{
super.onRestoreInstanceState(savedInstanceState);
if (btn_One != null) btn_One.setText(savedInstanceState.getString("btn_One").toString());
}
}
Also, the reason I say two tabs is so that I can demonstrate the problem in its simplest way possible so people can understand what I am trying to solve. My real project uses more then two tabs.
My "workaround" for the above issue would be to create a global "Bundle" poiner and pass the address of the one from onSaveInstanceState(Bundle savedInstanceState) to the global Bundle pointer and then call onRestoreInstanceState(*the_global_bundle_pointer*) when a tab gets reselected so that it restores the state.
What do you think?
Apart from as 'Learning from masters' states, there are better ways to build this structure up (page adapter / page viewer / fragments...) it should still work fine using your Bundle saveInstanceState correctly.
Can you post your code?
edit after seeing code:
You probably need to check your Bundle savedInstanceState in onCreate() instead.
This looks a bit like
#Override
public void onCreate(Bundle savedInstanceState) {
[...]
if(savedInstanceState != null){
//restore your stuff from the Bundle
}else{
// there is nothing to restore, so (down)load your data.
}
}
A good detailed explanation you can find around https://stackoverflow.com/a/6722854/1868384
Thank you both. It turns out with Android, once a new content layout is set to an Activity, the existing UI elements such as a text box, button of the previous layout etc are no longer available so trying to access them will cause an access violation. Once you set a content layout, it looks like the UI elements are rebuilt from scratch hence for losing all of the data. Well this is what I personally noticed and it certainly seems to be a default behavior. I don't know if this behavior can be changed.
I made my own "custom solution". All I did is create a new class, with a bunch of data members in it. Each time I set a new content to an Activity such as when I apply a layout when a tab is clicked or the device is rotated (onSaveInstance), I save/restore values from this class. I used to use the Bundle putInt, putString, putParcelable etc but I decided to take a different route. With my way, I am in-control and I know exactly what is going on with the code.

Categories

Resources