Calling addToBackStack() on a Fragment that is added via ActionBar - android

So basically, I have a simple Fragment that uses an AsyncTask to download a bitmap from a URL and then displays it inside an ImageView.
This is the Fragment's code:
public class TestFragment extends Fragment {
public TestFragment () {}
private String pictureUrl = "https://fbcdn-sphotos-d-a.akamaihd.net/hphotos-ak-prn1/532762_10150739418088211_1186720820_n.jpg";
private TextView sampleText;
private ImageView sampleImage;
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_view, container, false);
String textToSet = "Section Three";
sampleText = (TextView) root.findViewById(R.id.textView1);
sampleText.setText(textToSet);
sampleImage = (ImageView) root.findViewById(R.id.imageView1);
DownloadImageTask task = new DownloadImageTask(pictureUrl, root, sampleImage.getId());
task.execute();
return root;
}
}
This same fragment is displayed three times, one per each tab inside an ActionBar. The problem I am having is that each time the user selects a different tab, the previous one is destroyed, and when the user navigates back to the original tab, the content needs to be redownloaded. I want to make sure that the least amount of resources are consumed (saving both Data, and valuable overHead processing).
Here is the Fragment Container activity:
public class LayoutContainer extends Activity{
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout_container);
if (savedInstanceState != null) {
return;
}
final ActionBar actionBar = getActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
Tab tab = actionBar.newTab()
.setText(R.string.title_section1)
.setTabListener(new TabListener<TestFragment>(
this, "one", TestFragment.class));
actionBar.addTab(tab);
tab = actionBar.newTab()
.setText(R.string.title_section2)
.setTabListener(new TabListener<TestFragment>(
this, "two", TestFragment.class));
actionBar.addTab(tab);
tab = actionBar.newTab()
.setText(R.string.title_section3)
.setTabListener(new TabListener<TestFragment>(
this, "three", TestFragment.class));
actionBar.addTab(tab);
}
public static class TabListener<T extends Fragment> implements ActionBar.TabListener {
private Fragment mFragment;
private final Activity mActivity;
private final String mTag;
private final Class<T> mClass;
public TabListener(Activity activity, String tag, Class<T> clz) {
mActivity = activity;
mTag = tag;
mClass = clz;
}
public void onTabSelected(Tab tab, FragmentTransaction ft) {
if (mFragment == null) {
mFragment = Fragment.instantiate(mActivity, mClass.getName());
}
ft.replace(android.R.id.content, mFragment, mTag);
}
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
ft.addToBackStack(null);
ft.commit();
}
public void onTabReselected(Tab tab, FragmentTransaction ft) {
}
}
I currently have it set to call ft.replace() on the OnTabSelected() method, but I had previously tried with ft.add(). Same goes for my current ft.addToBackStack() call inside OnTabUnselected(), I initially had it set for simply calling ft.detach() but that was doing the same thing I described as being my problem. It loads that way, but it has to download the image every single time.
Now, I have tried calling addToBackStack() but the app force closes stating the FragmentTransaction CANNOT be added to the back stack. I also tried in the past to cache the image offline, to avoid data consumption, the app would still need to parse the image from a local URI and use processing power.
Any ideas I can try?
FYI, Yes, I know that the same fragment is loading the same image on all three tabs. This is merely a test project that is intended to help me better understand the lifecycle of Fragments.

So After a lot of reading I realized that the important code block is not done on your TabListener but in fact done on each Fragment that you use.
The fragment WILL be detached when navigating away from it, but that does not mean I cannot retain it state using onSaveInstanceState(...).
Here is simple implementation of what was needed to retain the Fragment's state after it being detached.
public class TimelineFragment extends ListFragment{
...
public TimelineFragment () {}
public void onSaveInstanceState(Bundle outState){
getActivity().getFragmentManager().putFragment(outState, TAG, this);
}
public void onRetoreInstanceState(Bundle inState){
getActivity().getFragmentManager().getFragment(inState, TAG);
}
...
}
We are saving the Fragment by calling putFragment(), and restoring it by calling getFragment(). Pretty darn simple, and I never thought of it.

What I do when a user need an image from the web I first check if I've cached it or the image is at the SD. If the image can't be found at SD or cache I download the image, store it to SD and add it to the cache.
It usually goes something like this:
Bitmap cacheBitmap = Database.getImageFromMemory(exercise.image, getActivity());
if(cacheBitmap != null) {
image.setImageBitmap(cacheBitmap);
progressBar.setVisibility(View.GONE);
}
Bitmap localBitmap = ImageDownloader.decodeSampledBitmap(exercise.image, width, height, getActivity());
if(localBitmap != null) {
image.setImageBitmap(localBitmap);
progressBar.setVisibility(View.GONE);
Database.addBitmapToMemoryCache(exercise.image, localBitmap, getActivity());
} else {
ImageDownloader imageDownloader = new ImageDownloader(this.getActivity().getApplicationContext(), image, progressBar, width, height);
imageDownloader.execute(exercise.image);
}
Now this is not all the code but it gives you an idea about how to handle it. If you need more code feel free to ask, but it seems like you are on the right track.

Related

How to update my fragment?

I'm passing data from fragment to another fragment with a bundle, but I have a map and I want to update this fragment and non replace it...this is the code in the MainActivity:
Mapped map = new Mapped();
final Bundle bundle = new Bundle();
bundle.putString("Position",one);
bundle.putString("ID",two);
map.setArguments(bundle);
getSupportFragmentManager().beginTransaction().replace(R.id.mapView, map).commit();
and every time it delete the old fragment...How can I do to update it and not replace?
EDIT: this is the code of MainActivity:
public class MainActivity extends FragmentActivity implements Connection.SendMessage
{
ViewPager Tab;
TabPagerAdapter TabAdapter;
ActionBar actionBar;
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TabAdapter = new TabPagerAdapter(getSupportFragmentManager());
Tab = (ViewPager)findViewById(R.id.pager);
Tab.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener()
{
public void onPageSelected(int position)
{
actionBar = getActionBar();
actionBar.setSelectedNavigationItem(position);
}
});
Tab.setAdapter(TabAdapter);
actionBar = getActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
ActionBar.TabListener tabListener = new ActionBar.TabListener()
{
public void onTabReselected(android.app.ActionBar.Tab tab,FragmentTransaction ft)
{
}
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft)
{
Tab.setCurrentItem(tab.getPosition());
}
public void onTabUnselected(android.app.ActionBar.Tab tab,FragmentTransaction ft)
{
}};
actionBar.addTab(actionBar.newTab().setText("Connessione").setTabListener(tabListener));
actionBar.addTab(actionBar.newTab().setText("Mappa").setTabListener(tabListener));
}
public void send(String one, String two)
{
Mapped map = new Mapped();
final Bundle bundle = new Bundle();
bundle.putString("Position",one);
bundle.putString("ID",two);
map.setArguments(bundle);
getSupportFragmentManager().beginTransaction().replace(R.id.mapView, map).commit();
}
}
The communication between fragments must be done through the parent FragmentActivity like documentation says:
Often you will want one Fragment to communicate with another, for example to change the content based on a user event. All Fragment-to-Fragment communication is done through the associated Activity. Two Fragments should never communicate directly.
The problem is how to refresh something in the ui. If you call replace to make the back navigation, in the OnCreateView of the new fragment you get the value of the parent activity. If you use the popbackstack to navigate backwards you should implement an interface.if you show the two fragments together, only use the fragment activity to refresh data.You can chage this:
getSupportFragmentManager().beginTransaction().replace(R.id.mapView, map).commit();
for this:
MyFragment myFragment = (MyFragment) getFragmentManager().findFragmentById(R.id.test_fragment);
if(myFragment!=null){
myFragment.update();
}
I'll give you some links to talk about this:
http://developer.android.com/training/basics/fragments/communicating.html
https://stackoverflow.com/a/9977370/944630
How to pass data between fragments
hope you help
You could try this solution:
add Mapped with a tag myMap:
Mapped map = new Mapped();
getSupportFragmentManager().beginTransaction().add(R.id.mapView, map, "myMap").commit();
make a method in Mapped, named addData(Bundle data), then you can pass data by this method when you need.
after data added to Mapped, then refresh it by:
Fragment mapFrg = getSupportFragmentManager().findFragmentByTag("myMap");
getSupportFragmentManager().beginTransaction().detach(mapFrg);
getSupportFragmentManager().beginTransaction().atach(mapFrg).commit();
in Mappde, in onCreateView(), each time check MapView is null or not
Hope this help.

Android SupportActionBar tabs disappear under Fragment

I'm using the Support Package v7 for adding Action Bar Tabs to my app.
When the tab's Fragment has a root view with height="fill_parent" the Fragment is covering my tabs.
When I’m changing the Fragment's root view to height="wrap_content" I can see the tab but my Fragment styling is affected.
My code for loading Fragments is the same as in Android Docs:
public class PicoTabListener<T extends Fragment> implements TabListener {
private Fragment mFragment;
private final Activity mActivity;
private final String mTag;
private final Class<T> mClass;
/** Constructor used each time a new tab is created.
* #param activity The host Activity, used to instantiate the fragment
* #param tag The identifier tag for the fragment
* #param clz The fragment's Class, used to instantiate the fragment
*/
public PicoTabListener(Activity activity, String tag, Class<T> clz) {
mActivity = activity;
mTag = tag;
mClass = clz;
}
/* The following are each of the ActionBar.TabListener callbacks */
public void onTabSelected(Tab tab, FragmentTransaction ft) {
// Check if the fragment is already initialized
if (mFragment == null) {
// If not, instantiate and add it to the activity
mFragment = Fragment.instantiate(mActivity, mClass.getName());
ft.add(android.R.id.content, mFragment, mTag);
} else {
// If it exists, simply attach it in order to show it
ft.attach(mFragment);
}
}
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
if (mFragment != null) {
// Detach the fragment, because another one is being attached
ft.detach(mFragment);
}
}
#Override
public void onTabReselected(Tab arg0, FragmentTransaction arg1) {
// do nothing
}
Isn't tab should always be in the top of the screen as the first layer?
Isn't tab should always be in the top of the screen as the first layer ?
No.
If you were using ActionBarSherlock, I think what you are doing would work. And if you use the native action bar implementation, I think what you are doing would work. However, you cannot do this with AppCompat:
ft.add(android.R.id.content, mFragment, mTag);
Use some container inside of your layout, like a FrameLayout, as the target of the transaction, not android.R.id.content. See this issue for more.

Cross-fragment communication

I am working on an app that has an "artist list", a list of a good chunk of music bands, performers and/or composers. They are sorted by genre and relative popularity.
From the main activity you go to a "band list" activity that has two tabs in it. One of them is a "genres" tab that has a list of the general music genres and when you click on each genre you get sent to the respective activity that also has two tabs in it. The first one is the "All" tabs and the other one's the "Popular" tab. Each of the tabs holds a fragment that contains a filtered sqlite database (by "filtered" I mean I have one big database that gets filtered to show what I want in different fragments rather than using a new database for each fragment/genre). So now I have 7 genres and 14 classes; 2 ("all" and "popular") for each genre.
What I would like to do now is reduce the number of classes to only two (all/popular) or, if possible, even one, by recycling the same fragment for all lists via filtering the database again. In order to do this I need to send a string from the "genres" fragment, containing the position of the pressed list item (genre) to the activity which contains the "All" and "Popular" tabs (fragments), which will then send that string further, to the fragments, which will use it to filter the sqlite database.
I need to add that I have, of course, read the Android Developers guide on fragment communication, but I do not know how to use their approach in my code, since I have created the fragments a little differently, including not defining them in the xml, so because of that I don't know how to use their getSupportFragmentManager().findFragmentById(R.id.article_fragment); method and some more things like that.
Here is the TabListener via which I am creating all fragments:
public class TabListener<T extends Fragment> implements ActionBar.TabListener {
private Fragment mFragment;
private final Activity mActivity;
private final String mTag;
private final Class<T> mClass;
public TabListener(Activity activity, String tag, Class<T> clz) {
mActivity = activity;
mTag = tag;
mClass = clz;
}
public void onTabSelected(Tab tab, FragmentTransaction ft) {
if (mFragment == null) {
mFragment = Fragment.instantiate(mActivity, mClass.getName());
ft.add(android.R.id.content, mFragment, mTag);
} else {
ft.attach(mFragment);
}
}
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
if (mFragment != null) {
ft.detach(mFragment);
}
}
public void onTabReselected(Tab tab, FragmentTransaction ft) {
}
}
Thanks,
Aleksa
If you created the fragments programmatically and added them with a tag, u can use the support FragmentManager method: findFragmentByTag(String tag)
After some more trying I eventually gave up on setArguments() and getArguments() and used this in the parent activity:
public int getPosition(){ return position; }
...and this in the child fragment:
position=((ParentActivity) getActivity()).getPosition();
I don't know if it is the most efficient method around but it works great and makes sending some simple data to a fragment a trivial thing for newcomers like me as it should be. :)

Get text from edit text fields that are in multiple tabs

I am trying to create an android application that uses tabs for an input form. Basically I want it set up so the user can enter some information on one tab, then either submit that information, or go to another tab and enter more information and then submit the information from both tabs.
I am using the action bar and fragments to display my tabs from one activity.
If I enter text in one tab then switch to the other, then switch back, the text is still there, but I can seem to figure out how to grab the text from the tab that is not currently shown when the submit button is clicked.
Is there any way of doing this?
Ok heres my code so far. I'm new to posting here sorry.
My tabs are set up using this:
ActionBar bar = getActionBar();
bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
//initiate tabs, set text and set listeners
Tab tab = bar.newTab();
tab.setText("Details");
tab.setTabListener(new TabListener<DetailsFragment>(this, "Details", DetailsFragment.class));
bar.addTab(tab);
tab = bar.newTab();
tab.setText("Comments");
tab.setTabListener(new TabListener<CommentsFragment>(this, "Details", CommentsFragment.class));
bar.addTab(tab);
My TabListener:
private class TabListener<T extends Fragment> implements ActionBar.TabListener{
private Fragment mFragment;
private final Activity mActivity;
private final String mTag;
private final Class<T> mClass;
public TabListener(Activity activity, String tag, Class<T> clz){
mActivity = activity;
mTag = tag;
mClass = clz;
}
public void onTabSelected(Tab tab, FragmentTransaction ft) {
//check if the fragment is already initialized
if(mFragment == null){
//if not, instantiate and add it to the activity
mFragment = Fragment.instantiate(mActivity, mClass.getName());
ft.add(android.R.id.content, mFragment, mTag);
}
else{
//if it exists, attach it in order to show it
ft.attach(mFragment);
}
}
My on click listener:
public void onClick(View v) {
titleText = (EditText)findViewById(R.id.textTitle);
authorText = (EditText)findViewById(R.id.textAuthor);
seriesText = (EditText)findViewById(R.id.textSeries);
pubText = (EditText)findViewById(R.id.textPublisher);
isbnText = (EditText)findViewById(R.id.textISBN);
commentsText = (EditText)findViewById(R.id.textComments);
String title = titleText.getText().toString();
String author = authorText.getText().toString();
String series = seriesText.getText().toString();
String pub = pubText.getText().toString();
String isbn = isbnText.getText().toString();
String comments = commentsText.getText().toString();
My fragment classes look like this:
public class DetailsFragment extends Fragment{
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
return inflater.inflate(R.layout.detailsfragment, container, false);
}
}
and an example of their layout:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/detailsTab"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_horizontal">
<EditText android:id="#+id/textTitle"
android:hint="#string/textTitle"
android:inputType="textCapWords"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="10"
android:layout_marginBottom="15dp"
android:layout_marginTop="25dp">
<requestFocus />
</EditText>
<EditText android:id="#+id/textAuthor"
android:hint="#string/textAuthor"
android:inputType="textCapWords"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="10"
android:layout_margin="15dp"/>
If there is any other code you want to see I can post that as well.
Ok so I figured out a way to get it to work how i want it to. I doubt its the best way of doing it but heres what I did.
In the onTabUnselected in the TabListener I added a function right before I detach the fragment to check which fragment is about to be detached and then save the data to variables stored in the activity.
public void onTabUnselected(Tab tab, FragmentTransaction ft)
{
if(mFragment != null)
{
saveData(mFragment);
//Detach the fragment
ft.detach(mFragment);
}
}
public void onTabReselected(Tab tab, FragmentTransaction ft) {
//do nothing
}
}
private void saveData(Fragment frag){
if(frag.getTag() == "Details")
{
titleText = (EditText)findViewById(R.id.textTitle);
title = titleText.getText().toString();
authorText = (EditText)findViewById(R.id.textAuthor);
author = authorText.getText().toString();
}
else if(frag.getTag() == "Comments")
{
commentsText = (EditText)findViewById(R.id.textComments);
comments = commentsText.getText().toString();
}
}
I still feel like there has to be a better way of doing this since the data is retained when i switch between tabs in the text boxes.

Using Fragments and TabListener the right way

My problem is about fragments with tabs. I have got one activity which basically has an empty layout and I add fragments to it dynamically. As soon as I start the activity I add a fragment with a listview to it and when I click on an item of the list I remove the fragment with the listview and add another fragment with details about it. Also some tabs are visible now. Clicking on one of these tabs shows other details about the item from the listview.
I do have working code but it seems that I'm hacking it altogether.
Here is some bits of it:
Activity:
// In onCreate:
#Override
protected void onCreate(Bundle savedInstanceState) {
// Check that the activity is using the layout version with
// the fragment_container FrameLayout
if (findViewById(R.id.fragment_container) != null) {
// However, if we're being restored from a previous state,
// then we don't need to do anything and should return or else
// we could end up with overlapping fragments.
if (savedInstanceState != null) {
return;
}
// Create an instance of FList
fList = new FList();
// Add the fragment to the 'fragment_container' FrameLayout
getFragmentManager().beginTransaction().add(R.id.fragment_container,fList).commit();
}
}
// After clicking on an item of the listview changeFragment() is called
public void changeFragment() {
FragmentTransaction ft = getFragmentManager().beginTransaction();
fSpeed = new FSpeed();
ft.add(R.id.fragment_container, fSpeed);
// this might be necessary
if (actionBar != null)
actionBar.selectTab(tabSpeed);
ft.commit();
}
// In the activity I also setup the ActionBar with tabs
private void setupActionBar() {
tabSpeed = actionBar.newTab();
// and a few more tabs...
tabSpeed.setTabListener(this);
actionBar.addTab(tabSpeed);
// But the tabs are not visible because of the navigation mode
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
}
// Here is the tabListener - very hacky!
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft)
if (fm == null)
fm = getFragmentManager();
Fragment f = null;
switch (tab.getPosition()) {
case 0:
if (fSpeed == null)
fSpeed = new FSpeed();
f = findFragment(fSpeed);
if (f != null) {
ft.setCustomAnimations(R.anim.fragment_alpha_0_1, R.anim.fragment_alpha_1_0);
ft.remove(f);
ft.add(R.id.fragment_container, fSpeed);
} else {
Log.d(TAG, "fSpeed is " + f);
return;
}
break;
case 1:
// more tabs - similar code
}
// the findFragment() method
private Fragment findFragment(Fragment selectedFragment) {
if (fm.findFragmentById(R.id.fragment_container) == fSpeed && selectedFragment != fSpeed)
return fSpeed;
// more ifs for other fragments
else
return null;
}
This all worked fine for the minute but after switching x times between the tabs things started looking funny, e.g. fList was visible, tab icons disappeared, ... I could track the bugs down and add some null checks but I think I went the wrong way. This is why I changed the TabListener with the following one:
// Still in activity
public static class MyTabListener<T extends Fragment> implements TabListener {
private Fragment mFragment;
private final Activity mActivity;
private final String mTag;
private final Class<T> mClass;
/**
* * Constructor used each time a new tab is created.
* * * #param
* activity * The host Activity, used to instantiate the fragment
* * #param
* tag * The identifier tag for the fragment
* * #param clz * The
* fragment's Class, used to instantiate the fragment
*/
public MyTabListener(Activity activity, String tag, Class<T> clz) {
mActivity = activity;
mTag = tag;
mClass = clz;
}
/* The following are each of the ActionBar.TabListener callbacks */
public void onTabSelected(Tab tab, FragmentTransaction ft) {
// Check if the fragment is already initialized
if (mFragment == null) {
// If not, instantiate and add it to the activity
mFragment = Fragment.instantiate(mActivity, mClass.getName());
ft.add(R.id.fragment_container, mFragment, mTag);
} else {
// If it exists, simply attach it in order to show it
ft.attach(mFragment);
}
}
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
if (mFragment != null) {
ft.detach(mFragment);
}
}
// The tabs are now assigned to MyTabListener
tabSpeed.setTabListener(new MyTabListener<FSpeed>(this, "speed", FSpeed.class));
Here is where the problem started. After clicking on an item from the listview with the new TabListener onTabSelected and onTabUnSelected are called in turns endlessly.
Question:
My question is how do you go from a fragment with a listview to another fragment after clicking on an item of the listview (and still use the same activity). Is my own written method changeFragment() the wrong approach? Still I would like to use the MyTabListener.
Note 1: I bought Commonsware's book to learn more about fragments but I couldn't find a more complex example with different fragments working together and also couldn't find how the back button is handled, or overridden. For example after clicking on the back button if fragment1, 2 or 3 are visible always show fragment4. If anybody found one in the book could you please tell me the chapter (name)/ page? If there isn't it would be very kind of the Common guys to provide one in the next update or so.
Note 2: Global variables were used in the project like fSpeed, tabSpeed, ...
Note 3: If you need more code or explanation please let me know in the comments. Thanks for helping!
Is my own written method changeFragment() the wrong approach?
You are adding a fragment, not replacing one. To replace a fragment, use replace(), not add().

Categories

Resources