I'm able to start a Fragment from an Activity.
However Im worried about potential problems with my implementation.
I have two fragments, FragmentA and FragmentB
And I have 3 activity classes, Activity1, Activity2, ResultActivity
public class NavigationTabs extends FragmentActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_tabs_2);
FragmentStatePagerAdapter adapter = new MyTabs(getSupportFragmentManager());
....
....
}
static class MyTabs extends FragmentStatePagerAdapter {
public MyTabs(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
switch(position)
{
case 0:
FragmentA FragA = new FragmentA();
return FragA;
case 1:
FragmentB FragB = new FragmentB();
return FragB;
......
......
}
}
^How I call FragmentA and FragmentB
FragmentA starts Activity1 via an intent.
Intent intent = new Intent(getActivity(), Activity1.class);
startActivity(intent);
Activity1 then passes the results of a counter to ResultActivity
ResultActivity starts(or returns to) FragmentA and sets SharedPreferences via onClick like this
public void onClick(View v) {
if(v.getId()== R.id.button_1){
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
Editor edit = sp.edit();
edit.putInt(passedSavePref, counter);
edit.commit();
finish();
}
}
FragmentB starts Activity2 via an intent.
Intent intent = new Intent(getActivity(), Activity2.class);
startActivity(intent);
Activity2 then passes the results of a counter to ResultActivity
ResultActivity starts(returns to) FragmentB and sets SharedPreferences via onClick like this
public void onClick(View v) {
if(v.getId()== R.id.button_1){
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
Editor edit = sp.edit();
edit.putInt(passedSavePref, counter);
edit.commit();
finish();
}
}
This all works for my needs. GC seems fine, its freeing and allocating memory.
ResultActivty returns to the correct Fragment, and it sets the SavedPreferences correctly.
Yet it seems very bad implementation.
For starters, while searching through other questions I've read, "Don't start an Activity directly from a Fragment" the poster also linked to the proper implementation here https://developer.android.com/guide/components/fragments.html#EventCallbacks
I've tried calling Activity1 from FragmentA like this, but I don't really see a difference in behavior or performance
Intent intent = new Intent(getActivity(), Activity1.class);
startActivity(intent);
getActivity().getSupportFragmentManager().popBackStack();
So my question is, do I need to finish/remove FragmentA when I start Activity1, then start FragmentA again from ResultActivityusing something like
FragmentTransaction transaction = getFragmentManager().beginTransaction();
Thanks in advance.
EDIT So what I was trying to was to either kill/finish/pop FragmentA so that I could re-start it from ResultActivity.
The reason I was tying to do that was because my savedPreferences were not loading when I was going back to FragmentA from ResultActivity.(well they were saving and loading correctly, but I couldn't see them)
As I understand it from the docs,Fragments go on pause. So calling my loadPreferences method onResume(); loaded my SavedPreferences.
Not marking this as an answer, because I did not implement any of the standard/proper practices of dealing with Fragments popBackStack(); FragmentTransactions etc
Quote: "Don't start an Activity directly from a Fragment"
I read the poster who wrote this, and I strongly disagree. His rationale is that it reduces the modularity of the fragment, and he believes you should impelment an interface to call back to the activity.
I disagree. It doesn't reduce modularity, in fact it increases it. Why implement a layer of abstraction to do something the fragment is intended to do in every implementation? Why re-write the same code in every activity, when it can be modularized in the fragment? In fact fragments wouldn't have their own specialized functions for starting activities if this was against design principles.
For instance, if you start an activity using fragment.startAcitivtyForResult(), the fragment is put directly as the onActivityResult receiver. Not so if you use your activity instance to directly start the new activity.
You do not need to remove framgents before starting a new Activity
Android will pause/stop your fragments, and potentially destroy them and the underlying activity as well if need be. Android is constantly destroying and recreating activities and fragments, like every time you change the orientation on your screen - the default settings has your activity and fragments commit mass suicide.
This is why functions like OnSaveInstanceState() are so important, because they let your activities and fragments return to a saved state. The general rule of android programming is your activity / fragments should be able to respond to spontaneous death gracefully.
Related
Scenario:
Activity A opens Activity B, Activity B has two fragments a and b in a viewpager using FragmentPagerAdapter, fragments a and b has some radio buttons and check boxes where the user interacts with;
I need to store the buttons and checkboxes statuses in the fragments even if I left Activity B to A
My Attempts:
1- removed super.onBack() pressed to force the system into calling onSavedInstanceState in the activity, but still couldn't save fragments to it as am instantiating them in the adapter and do not know how to get the same object created of them to activity
2- tried the onSaveInstanceState(), onViewStateRestored() in each fragment and onSavedInstance never got called using setRetainInstance(true) in the oncreate(); then forced calling onSaveInstanceState() by calling it onPause()
I Read most of available solutions on stack and non of them worked, My Code is as follows;
public class FilterPagerAdapter extends FragmentPagerAdapter {
public final static int KEYWORDS_TAB = 0;
public final static int AREAS_TAB = 1;
private int tabCount;
private Context context;
public FilterPagerAdapter(FragmentManager fm, int tabCount, Context context) {
super(fm);
this.tabCount = tabCount;
this.context = context;
}
#Override
public Fragment getItem(int position) {
Fragment fragment;
switch (position) {
case KEYWORDS_TAB:
fragment = KeyWordsFragment.newInstance();
break;
case AREAS_TAB:
fragment = AreasFragment.newInstance();
break;
default:
return null;
}
return fragment;
}
#Override
public int getCount() {
return tabCount;
}
#Override
public CharSequence getPageTitle(int position) {
String tabTitle = "";
switch (position) {
case KEYWORDS_TAB:
tabTitle = context.getResources().getString(R.string.tab_keywords);
break;
case AREAS_TAB:
tabTitle = context.getResources().getString(R.string.tab_area);
break;
default:
}
return tabTitle;
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_filters);
ButterKnife.bind(this);
mAreas = new Areas();
mKeywords = new KeyWords();
viewControllers();
}
private void viewControllers() {
tabLayout.addTab(tabLayout.newTab().setText(R.string.tab_keywords), CATEGORIES_TAB);
tabLayout.addTab(tabLayout.newTab().setText(R.string.tab_area), BRANDS_TAB);
tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
tabLayout.addOnTabSelectedListener(this);
filterPagerAdapter = new FilterPagerAdapter(getSupportFragmentManager(),
tabLayout.getTabCount(), this);
viewPager.setAdapter(filterPagerAdapter);
tabLayout.setupWithViewPager(viewPager);
}
#Override
public void onBackPressed() {
startActivity(new Intent(FiltersActivity.this, BranchesActivity.class));
}
#Override
public void onStop() {
super.onStop();
tabLayout.removeOnTabSelectedListener(this);
}
}
First off, I would recommend not forcing any call to onSavedInstanceState or any of the other lifecycle callbacks. It's better to fit to the flow of the lifecycle than to make the lifecycle fit your flow.
Second, there most likely isn't a reason to use setRetainInstance() on Fragments like this. This method is useful for creating viewless Fragments and store large data-sets. Going beyond that use-case tends to bring a lot of headaches.
So, with those out of the way, there should be a couple of ways to solve this, and they are dependent on the type of data it represents.
Method 1:
One way, which is similar to what you tried with #1, is advance to the next Activity (in this case ActivityA) rather than going back when the user presses back. This will pause and possibly stop ActivityB as well as use the onSaveInstanceState() method in which you can store the state of your Fragments.
The FragmentPagerAdapter will only call getItem() when it needs a new Fragment. All the Fragments that are already created will then be in the FragmentManager. Each Fragment will have to handle its own state. There's no need for the Activity or the Adapter to know about it. Each previously created Fragment will have onSavedInstanceState() called, and the bundle returned from that will be passed in onCreate(bundle) and onCreateView(bundle).
Method 2:
The above works for a lot of cases where the data is not very important. You could get away with it being wiped, and keeping the state is more for convenience. If the user was away from ActivityB for a while for whatever reason, the system could dump it from the stack. If that happens, then it could still be destroyed and the state reverts. In this case, you'll have to use SharedPreferences or some other more permanent data storage to save and restore the state.
Method 3:
I noticed that the name of ActivityB is actually FiltersActivity. I'm making the assumption that FiltersActivity is an Activity in which the user selects filters that get sent back to BranchesActivity. If you don't want to make these persistent, then another method then would be to simply pass the information back and forth between the two.
BranchesActivity will pass in the current filters to FiltersActivity.
FiltersActivity then defaults all its settings to the filters that were passed in.
FiltersActivity lets the user select the new set of filters.
The users presses "back" on FiltersActivity which returns the selected filters.
BranchesActivity now has new filters. When the user wants to change the filters, BranchesActivity will go back to step 1.
I have this scenario in my app:
One activity(A) with multiple fragments calling another activity(B) at some point.
Flow for that goes like this: F1 => F2 => F3 => B or F1 => F2 => B where F(n) represents fragment.After I finish activity B it returns me to F3 or F2 but my goal is to show user F1 so I tried sending event via event bus and replacing any other fragment with F1,note that I'm adding every fragment to backstack.So I succeed with it but if I call fragment F2 or F3 application crashes also sometimes I get "IllegalStateException: Can not perform this action after onSaveInstanceState".
So after trying a lot of approaches I simply did this :
public void onClick(View v){
//started activity B
//replaced current fragment with F1
}
The end result of this was seeing F1 before activity B ,and everything else worked fine without crashing.So to solve that glitch I replaced fragment 100 ms after activity B is started.
public void onClick(View v){
//started activity B
new Handler().(new Runable(){
#Override
public void run()
{
//replaced current fragment with F1
}
},100);
}
But I feel this is ugly way to solve this problem and I want to ask you if there is better solution?
EDIT:
I was inspired by spcial answer so I did similar thing with states.
In activity A I have two variables.
boolean wasAnotherActivityCalled=false;
String showFragment=null;
In my fragment I have this :
public void onClick(View v){
//started activity B
getActivity().wasAnotherActivityCalled=true;
getActivity().showFragment=FragmentOne.class.getSimpleName();
}
in activity A I have this :
#Override
protected void onResumeFragments() {
super.onResumeFragments();
if(wasAnotherActivityCalled)
{
if(showFragment.equals(F1.class.getSimpleName()))
{ //do your logic here}
wasAnotherActivityCalled=false;
showFragment=null;
}
}
I have something similiar in my App. What I did was to use a simple "state-machine" where I have an int attribute which represents the current state (0 = Fragment1, 1 = Fragment2...etc) and an ArrayList with all fragments which I need. If I have to switch the fragment I also increment the state and just load the fragment from the ArrayList.
In my onPause() method I save the state in sharedPreferences() and in the onResume() method I load the state from sharedPreferences() and do a initFragment(state) where I just replace the fragmentLayout with the fragment from fragmentArray[state] :-)
With this behaviour I could handle backstack on my own, can go back and furth and save the state of the current needed fragment everytime the user changes the activity and comes back. Furthermore I don't commit the fragments into backstack because it is already handled through myself.
I hope I could help you with this.
Dont add your fragment into backstack while commiting it
I have a two class named Stock Details extends Fragment and another one is Stock info extends activity,
when I was trying to go back to my Stock details pages from Stock info pages it shows "Unfortunately,Application has stopped",but in my program I use
Intent intent = new Intent(this,StockDetails.class);
startActivity(intent);
finish();
for go back to fragment. but it does not work. help me to solve this problem
I am the beginner for android.
kindly help me to go back from an Activity to fragment
you do not need to start activity for StoreDetail as it is a subclass of Fragment not Activity.This is the reason that your app is crashed.
Now moving to your question if you want to go back to the fragment from the activty : Stock INFO you just need to call finish() it will finish the current activity(Stock Info) and the fragment which is in background will be resumed.I had same problem and solved by this way .This is the onCreate Method of Activity(in your case it is for StockInfo class).Have a look:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setmCallBack(new IResultCallback() {
#Override
public void result(Result lastResult) {
if (lastResult!=null) {
finish();// by this line I have killed the current activity
}
else
{
Toast.makeText(getActivity(), "NotScan: ", Toast.LENGTH_SHORT).show();
}
}
});
}
Ok look,
startActivity(intent);
this method name startActivity. So it will start another activity not fragment.
You can See & read the fragment:
http://developer.android.com/guide/components/fragments.html
Add fragment to go back to manually to the backstack.
FragmentTransaction transaction = getFragmentManager().beginTransaction();
YourFragmentName myFragment = new YourFragmentName();
transaction.replace(R.id.fragment_container, myFragment);
transaction.addToBackStack(null);
transaction.commit();
And There is several methods here. Just take a look:-
http://developer.android.com/reference/android/app/FragmentManager.html#popBackStack()
If you want to go back to the fragment , you can do this :
getFragmentManager().popBackStack();
This is unlikely but it would potentially save me a lot of time to re-write the same code.
I want to implement a UI using alert-type service (like Chathead) yet I'd still like to use my fragments. Is it possible? I know I can add views to the window but fragments?
Fragments are part of the activity, so they cannot replace activity. Though they behave like activity, they cannot stand themselves. Its like view cannot itself act like activity.
From Android Developers:
A Fragment represents a behavior or a portion of user interface in an
Activity. You can combine multiple fragments in a single activity to
build a multi-pane UI and reuse a fragment in multiple activities. You
can think of a fragment as a modular section of an activity, which has
its own lifecycle, receives its own input events, and which you can
add or remove while the activity is running (sort of like a "sub
activity" that you can reuse in different activities).
I hope it is helpful to you.
Well as people have pointed out you can't, but, you can always create
some sort of fragment wrapper.
For example purposes:
public class ActivityFragmentWrapper extends FragmentActivity {
public static final String KEY_FRAGMENT_CLASS = "keyFragmentClass";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getIntent().getExtras() != null) {
String fragmentClass = (String) getIntent().getExtras().get(KEY_FRAGMENT_CLASS);
try {
Class<?> cls = Class.forName(fragmentClass);
Constructor<?> constructor = cls.getConstructor();
Fragment fragment = (Fragment) constructor.newInstance();
// do some managing or add fragment to activity
getFragmentManager().beginTransaction().add(fragment, "bla").commit();
} catch (Exception LetsHopeWeCanIgnoreThis) {
}
}
}
public static void startActivityWithFragment(Context context, String classPathName) {
Intent intent = new Intent(context, ActivityFragmentWrapper.class);
intent.putExtra(KEY_FRAGMENT_CLASS, classPathName);
context.startActivity(intent);
}
}
You can start it like:
ActivityFragmentWrapper.startActivityWithFragment(context, SomeSpecificFragment.class.getCanonicalName().toString());
Of course if your fragment has another constructor you have to retrieve different
one, but that part gets easier.
No, Fragments can't exist without an Activity. They need an activity for their entry point otherwise they can't initiate their UI components and their lifecycle can't go beyond onAttach and onCreateView
I am attempting to expand my application by adding a TabHost and some tabs to navigate extra features. The current app basically searches a database. The current application workflow:
App loads to a login screen
User logs in
User gets a search form and inputs data, presses "search"
Search loads a list activity of results...
With the new tabs, there is a separate tab for searching. I want all the seach activities to remain inside that tab group. So I've created an activity group to handle all of these:
public class searchGroup extends ActivityGroup {
public static searchGroup group;
private ArrayList<View> history;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.history = new ArrayList<View>();
group = this;
View view = getLocalActivityManager().startActivity("search", new Intent(this,search.class).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)).getDecorView();
replaceView(view);
}
public void replaceView(View v) {
history.add(v);
setContentView(v);
}
public void back() {
if(history.size() > 0) {
history.remove(history.size()-1);
setContentView(history.get(history.size()-1));
}else {
finish();
}
}
#Override
public void onBackPressed() {
searchGroup.group.back();
return;
}
}
In my search activity's Search button onClickListener:
view = searchGroup.group.getLocalActivityManager().startActivity("search_results",new Intent(search.this, search_results.class).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)).getDecorView();
searchGroup.group.replaceView(view);
This is where I get the crash:
02-11 13:43:49.481: E/AndroidRuntime(1165): java.lang.RuntimeException: Unable to start activity
ComponentInfo{com.myApp/com.myApp.search_results}:
android.view.WindowManager$BadTokenException: Unable to add window --
token android.app.LocalActivityManager$LocalActivityRecord#40543360 is
not valid; is your activity running?
However, if I uncomment a line from the search_result activity's onCreate:
new LoadSearches().execute();
no crash, but I get nothing obviously. LoadSearches() is an AsyncTask that does the heavy lifting of going out to the server and running the search string and then populating the returned data into the ListActivity in onPostExecute().
I don't quite understand why its crashing here and not normally when I switch activities. How should I tackle this? Is there a better way? I've read a little bit about Fragments but haven't done anything with it yet.
I have decided, after much pulling my hair out, to go with fragments. Some resources I found useful for converting my existing app to use Fragments and tabs:
Fragments in Android 2.2.1, 2.3, 2.0. Is this possible?
http://www.e-nature.ch/tech/?p=55
http://thepseudocoder.wordpress.com/2011/10/04/android-tabs-the-fragment-way/
I also had an issue with pass data between my activities. The way to pass data between activities using an intent/bundle doesn't really work the same but can modified slightly and still work.
The old way (passing data from Activity1 to Activity2):
Activity1
Intent myIntent = new Intent(search.this, search_results.class);
Bundle b = new Bundle();
b.putString("SEARCHSTRING", strSearch);
myIntent.putExtras(b);
startActivityForResult(myIntent, 0);
Activity2
Bundle b = getIntent().getExtras();
strSearch = b.getString("SEARCHSTRING");
Using fragments I had to create an initializer for Activity2:
public search_results newInstance(String strSearch){
search_results f = new search_results();
Bundle b = new Bundle();
b.putString("SEARCHSTRING", strSearch);
f.setArguments(b);
return f;
}
using this, the new method using Fragments:
Avtivity1
Fragment newFragment = new search_results().newInstance(strSearch);
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.realtabcontent, newFragment);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.addToBackStack(null);
ft.commit();
Activity2 (onCreateView)
Bundle b = getArguments();
strSearch = b.getString("SEARCHSTRING");
I hope this helps someone as it was difficult for me to find all this information in one spot.