I have a simple problem.
My problem is that I have two activities:
Activity A
Activity B
In Activity A I display 4-5 fragments. That is the main activity (Navigation Drawer) so I display 4-5 fragments in it.
From all fragments it redirects to Activity B.
But I want to display the last opened fragment when I come back from Activity B.
Now it directly opens the first fragment, which is the default. I want to open the last opened fragment when the user returns to the first activity.
Please help me...
You can use onSaveInstanceState in Activity A to save information about last opened fragment and onRestoreInstanceState/onCreate to restore this information. For example:
private static final String LAST_OPENED_FRAGMENT_REF = "LAST_OPENED_FRAGMENT_REF";
private static final int HOME_FRAGMENT = 0;
private static final int OTHER_FRAGMENT = 1;
private int currentOpenedFragment = HOME_FRAGMENT;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
if (savedInstanceState != null) {
currentOpenedFragment = savedInstanceState.getInt(LAST_OPENED_FRAGMENT_REF);
}
if (navigationView != null) {
Fragment fragment = initFragmentByType(currentOpenedFragment);
getSupportFragmentManager()
.beginTransaction()
.add(R.id.fragment_container, fragment)
.commit();
setupDrawerContent(navigationView);
}
...
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(LAST_OPENED_FRAGMENT_REF, currentOpenedFragment);
}
private Fragment initFragmentByType(int type) {
switch(type) {
case HOME_FRAGMENT: return new Home();
case OTHER_FRAGMENT: return new Other();
default: throw new IllegalArgumentException("There is no type: " + type);
}
}
And don't forget to update currentOpenedFragment field in onNavigationItemSelected callback.
If your first activity changes with the second one, then fragments of the former activity will destroy themself because of the activity lifecycle.
You should just open first activity using startActivity and store the last active fragment before it goes the letter activity.
You don't use finish while call second activity. For exp: `
Intent i = new Intent(getApplicationContext(), ActivityB.class);
startActivity(i);
//finish(); `
Related
I have a callback interface.
public interface RowCallback {
void callOnRowAction(String action);
}
My fragment FragmentA is attached to ActivityA in which FragmentA is defined as follows:
class FragmentA extends Fragment implements RowCallback {
#Override
void callOnRowAction(String action) {
. . . Custom implementation . . .
}
... Other methods . . .
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_crime_list, container, false);
mButton = view.findViewById(R.id.my_button);
mButton.setOnClickListener((view) -> {
// I want to pass the callback instance (which is FragmentA) to activity B.
Intent intent = new Intent(getActivity(), ActivityB.class);
. . . Some more intent initialisation. . .
startActivity(intent);
});
return view;
}
}
ActivityB source Code
public class ActivityB extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragment);
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentById(R.id.fragment_container);
if (fragment == null) {
fragment = createFragment(getIntent());
fm.beginTransaction().add(R.id.fragment_container, fragment).commit();
}
}
private Fragment createFragment(Intent intent) {
FragmentB fragment = new FragmentB();
Bundle bundle = new Bundle();
. . . . initialise bundle . . .
// I want to pass the callback instance from the intent to the callback function.
// In this example, the callback instance would be an instance of fragment A.
fragment.setArguments(bundle);
return fragment;
}
}
In an instance of FragmentB, I want to invoke a call back method should a logical condition be satisfied.I want to return to ActivityA/FragmentA after the callback function has been invoked by calling getActivity().finish();
How can I achieve something like this?
Implement function in within fragment which you want get call back after execute
public void testCallBack(Callback callback) {
callback.callOnRowAction("callback");
}
Implement this in Activity class
public void getCallBack() {
FragmentA.newInstance().testCallBack(new FragmentA.Callback() {
#Override
public void callOnRowAction(String action) {
//you can do something
}
});
}
if you want to excute testCallBack method when the function start you can call this inside onCreate method
I will split my answer:
If you want a callback for one time:
Fragment A OnclickListener
will call to his Activity (lets say A).
Activity A will do
StartActivityForResult(intent) to Activity B.
Activity B will
attach Fragment B to himself.
Fragment B OnclickListener will
update his Activity (B).
Activity B will return IntentResult and Activity A will catch it on onActivityResult.
If you want a callback for multiple times:
You can use the architecture of answer 1 - with Activity communication using LocalBroadcastManager
An example to Activity communication using LocalBroadcastManager
You could inside the fragment add
if(getParentFragment() instanceof Activity1)
((Activity1) getParentFragment()).setRowCallback(this);
else if(getParentFragment() instanceof Activity2)
((Activity2) getParentFragment()).setRowCallback(this);
the fragment will automatically call the callback of his parent activity
I'm getting an NPE when I start an activity in my application. It doesn't happen right away when I boot my phone and start debugging on it. After several dozen new builds are pushed to my phone it eventually starts crashing with this error every single time. I can remedy it temporarily by opening a few other activities first before I activate the errant one.
Any ideas what could cause this? It's a somewhat complicated Activity with a DrawerLayout, and a fragment that contains a SwipeRefreshLayout with a ListView.
Exception
java.lang.RuntimeException: Unable to start activity ComponentInfo{}: java.lang.NullPointerException: Attempt to read from field 'boolean android.support.v4.app.BackStackRecord.mAddToBackStack' on a null object reference
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2298)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2360)
at android.app.ActivityThread.access$800(ActivityThread.java:144)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1279)
Activity Code
public class TastingsActivity extends BaseActivity implements TastingListFragment.Callbacks {
public static final String TAG = TastingsActivity.class.getSimpleName();
private TastingListFragment mTastingListFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tastings);
//create fragment
// Check that the activity is using the layout version with
// the fragment_container FrameLayout
if (findViewById(R.id.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 a new Fragment to be placed in the activity layout
TastingListFragment fragment = new TastingListFragment();
// In case this activity was started with special instructions from an
// Intent, pass the Intent's extras to the fragment as arguments
fragment.setArguments(getIntent().getExtras());
// Add the fragment to the 'fragment_container' FrameLayout
getSupportFragmentManager().beginTransaction()
.add(R.id.container, fragment).commit();
}
}
#Override
protected int getSelfNavDrawerItem() {
return NAVDRAWER_ITEM_TASTINGS;
}
//================================================================================
// Handlers
//================================================================================
#Override
public void onTastingSelected(Tasting tasting) {
Intent intent = new Intent(this, TastingActivity.class);
intent.putExtra(TastingDetailsFragment.EXTRA_TASTING_ID, tasting.getId());
startActivity(intent);
}
}
This might have already answered but I am still troubling with a function like this. Let's say I have activity A and activity B. B holds a viewpager with several fragments in it. I would like to call a function in the fragment held by activity B from activity A.
I used callbacks many times to communicate between activites and fragments but every single time it was only the fragment and its holder activity. I do not want to make a static method (the callback listener cannot be static anyway) so it causes a headache for me. The simple static solution to make a static method in the fragment and have it called from the other actually works very well, but I am not sure if it was a good idea as I need to change several things static.
So communicating between Activity B and its fragments is ok, but I cannot call this method in Activity A.
Activity B:
public class ActivityB extends FragmentActivity implements Fragment1.OnWhateverListener
{
...
#Override
public void onWhateverSelected(int position) {
//stuff, here I can call any function in Fragment 1
}
}
The following code snippet is a wrong solution (doesnt even work) but makes a better picture what I would like to do.
Activity A:
ActivityB ab = new ActivityB ();
ab.onWhateverSelected(number);
So how can I do this?
Thank you!
EDIT
Activity A: the method I call
Bundle args = new Bundle();
args.putString("ID", id); // the data to send
Intent frag_args = new Intent(Intent.ACTION_VIEW);
frag_args.setClass(this, MainActivity.class);
frag_args.putExtra("args", args);
startActivity(frag_args);
Activity B:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
...
processIntent(getIntent()); //last line of onCreate, always gets called here
}
#Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
processIntent(intent); // this never gets called here only in OnCreate
}
private void processIntent(Intent intent) {
Bundle args = intent.getBundleExtra("args");
if (args != null) { // check if ActivityB is started to pass data to fragments
String id = args.getString("ID");
Log.i("ID_FROM", "id: " + id); //works well
if (id != null) {
List<Fragment> fragments = new ArrayList<Fragment>();
fragments = getSupportFragmentManager().getFragments();
//NULLPOINTER for the following line
FragmentMainDiscover fr = (FragmentMainDiscover) fragments.get(0);
fr.RefreshHoverView(id);
}
}
}
You are right to stay away from statics. Way too risky, for visual objects that may or may not be on screen.
I would recommend going through activity B, since it is the parent of your target fragment. Create an Intent that starts activity B, and include an intent extra that tells activity B what it should do to the target fragment. Then activity B can make sure that the fragment is showing, and pass the information on to it.
One other idea to pass the info to the fragment is to use setArguments, rather than direct calls. This is a nice approach because Android will restore the arguments automatically if the activity and its fragments are removed from memory.
Does this make sense? Do you want the code?
EDIT
To use arguments, you still need to have activity A go through activity B. This is because activity A doesn't know if activity B, and all its fragments, is running unless it sends it an Intent. But you can include data targeted for the fragments, by putting them inside the intent. Like this:
public class ActivityA extends Activity {
public static final String KEY_FRAG = "frag"; // tells activity which fragment gets the args
public static final String KEY_ARGS = "args";
public static final String KEY_MY_PROPERTY = "myProperty";
public void foo() {
Bundle args = new Bundle();
args.putString(KEY_FRAG, "frag1Tag"); // which fragment gets the data
args.putCharSequence(KEY_MY_PROPERTY, "someValue"); // the data to send
// Send data via an Intent, to make sure ActivityB is running
Intent frag_args = new Intent(Intent.ACTION_VIEW);
frag_args.setClass(this, ActivityB.class);
frag_args.putExtra(KEY_ARGS, args);
startActivity(frag_args);
}
}
public class ActivityB extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//TODO configure activity including fragments
processIntent(getIntent()); // this call is in case ActivityB was not yet running
}
#Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
processIntent(intent); // this call is in case ActivityB was already running
}
private void processIntent(Intent intent) {
Bundle args = intent.getBundleExtra(ActivityA.KEY_ARGS);
if (args != null) { // check if ActivityB is started to pass data to fragments
String fragTag = args.getString(ActivityA.KEY_FRAG);
if (fragTag != null) {
Fragment frag = getSupportFragmentManager().findFragmentByTag(fragTag);
frag.setArguments(args);
//TODO either show the fragment, or call a method on it to let it know it has new arguments
}
}
}
}
public class Fragment1 extends Fragment {
public static final String TAG = "frag1Tag";
#Override
public void onResume() {
super.onResume();
Bundle args = getArguments();
String value = args.getString(ActivityA.KEY_MY_PROPERTY);
...
}
}
I have a Working model of fragments, when i was debugging the code i saw that the Fragment onCreate is being called 4 times.
Below is my code:
MyFragmentActivity
class MyFragmentActivity extends FragmentActivity{
#Override
public void onCreate(Bundle savedInstanceState) {
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction().replace(fragmentID, new MyListFragmentt())
.replace(detailFragmentID, new MyDetailFragment()).commit();
}
}
#Override
protected void onRestart() {
getSupportFragmentManager().beginTransaction().replace(detailFragmentID, new MyDetailFragment()).commitAllowingStateLoss();
}
}
MyDetailFragment.class
class MyDetailFragment extends Fragment{
// has method like oncreate(),onCreateView(),onSaveInstanceState()
}
How my oncreate of MyDetailFragment is called ? When i go to some other activity and come back and then tilt the device only then oncreate and onSaveInstanceState of MyDetailFragment is called multiple times.
How can i solve this, i have looked into few posts on SO but it says that we need use HIDE,Show methods and other things ? but What is the proper soultion to this ?
EDIT
When i am coming back from previous activity, my data in the MyDetailFragment needs to be refreshed.
Try this
MyDetailFragment fragment = new MyDetailFragment();
#Override
public void onCreate(Bundle savedInstanceState) {
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction().replace(fragmentID, new MyListFragmentt())
.replace(detailFragmentID, fragment).commit();
}
}
#Override
protected void onRestart() {
if(fragment != null) {
getSupportFragmentManager().beginTransaction().replace(detailFragmentID, fragment).commitAllowingStateLoss();
}
}
i think ur recreating fragments multiple times, u do new MyListFragment everytime on onCreate function, call findFragmentByTag to get the existing fragment and set that, if null (first time) then create one
/here is some code mate, if this doesnt work and ur app has single fragment better to just create xml and have only a fragment tag in it, and set that xml in setContentView function*/
// declare following member variable
MyFragment _fragment;
// in onCreate function, call this method
private void setupFragment()
{
_fragment = (MyFragment)getFragmentManager().findFragmentByTag("MyFragment");
if(null == _fragment)
{
_fragment = new MyFragment();
}
// now do the fragment transaction
FragmentTransaction trans = getFragmentManager().beginTransaction();
trans.add(containerId, _fragment, "MyFragment"); // here tag is important
trans.commit();
}
I have main activity which embeds fragment:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
vd = VirtualDatabaseTableProvider.getInstance(getApplicationContext());
fm = getSupportFragmentManager();
fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
//Create Layout and add fragments
setContentView(R.layout.main_window);
ListFragment ListFragment= new ListFragment();
android.support.v4.app.FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.fragment_pane, ListFragment, "List");
//ft.replace(R.id.fragment_pane, ListFragment);
ft.addToBackStack(null);
ft.commit();
//Initialising buttons
imgBtnFontInc = (ImageButton) findViewById(R.id.ImgBtnUpFont);
imgBtnFontInc.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(textViewAttached){
try{
//Some text Resize
}
}catch (NullPointerException npe){
Log.e(TAG, "Error on calling Text resize");
Log.e(TAG, npe.getMessage());
Log.e(TAG, npe.getStackTrace().toString());
}
}
}
}
);
/* More Buttons code..... */
imgBtnFontDec.setVisibility(View.GONE);
imgBtnFontInc.setVisibility(View.GONE);
/* Some Saved State handling to recover detailed text Screen*/
if(savedInstanceState != null){
if (savedInstanceState.containsKey("UUID")){
try{
String uuid = savedInstanceState.getString("UUID");
if (uuid != null){
iniTextScreen(uuid);
}
}catch (Exception e){
Log.e(TAG, "Unable To return text");
}
}
}
Text is initialised with function:
private void initTextScreen(String StringID){
Bundle Data = new Bundle();
Data.putString("UUID", StringID);
TextScreenFragment TextFragment = new TextScreenFragment();
TextFragment.setArg1ments(Data);
if(fm == null){
fm = getSupportFragmentManager();
}
android.support.v4.app.FragmentTransaction ft = fm.beginTransaction();
ft.setCustomAnimations( R.anim.animation_enter, R.anim.animation_exit);
ft.replace(R.id.fragment_pane, TextFragment, "TextFragment");
ft.addToBackStack(null);
ft.commit();
}
I handled Buttons visibility in main activity with simple Callback from TextScreenFragment. Callback in main activity:
public void onTextViewAttached() {
textViewAttached = true;
MainActivity.this.imgBtnFontDec.setVisibility(View.VISIBLE);
MainActivity.this.imgBtnFontInc.setVisibility(View.VISIBLE);
}
Callback called in TextScreenFragment:
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (!(activity instanceof Callbacks)) {
throw new IllegalStateException(
"Activity must implement fragment's callbacks.");
} else {
listener = (Callbacks) activity;
listener.onTextViewAttached();
}
}
public interface Callbacks {
/**
* Callback for when an item has been selected.
*/
public void onTextViewAttached();
}
It works, however when I put android phone is switch potrait/landscape mode: onAttached in fragment get called way before onCreate in main Activity and Button objects.
How can fragment be attached on main activity before even onCreate is called in main activity?
I attached a particular fragment at very end of onCreate method after buttons were already initialized,but why onAttach in fragment is called before even I attach fragment and get null exception, because button objects were not initialized in onCreate? How it is even possible?
When I comment out:
`// MainActivity.this.imgBtnFontDec.setVisibility(View.VISIBLE);
//MainActivity.this.imgBtnFontInc.setVisibility(View.VISIBLE);`
in callback function public void onTextViewAttached(), no more crashes, but still I noticed that onAttach is called before main activity created and is called twice:
one time with uninitialised activity from hell knows were (every element of main activity is either null or has default values), second time when fragment is properly attached from main activity onCreate.
I made conclusion that on orientation switch fragments gets atached to uninitialised activity. Am I missing something, on orientation change should I call some other function before onCreate in main activity to get buttons from layout?
Is it some kind of fragment automated attach behaviour which I am not aware of and I could take advantage of?
What is life cycle of activity with fragment attached to it, because onAttach called in fragment before even main activity is created seems counter intuitive.