Android Fragment not visible - android

I have added a background service to my application which creates a notificaion when a new item is added to my application. When pressing the notification the user is taken into the application and the intent passes an object which allows the application to select the newly added item.
The application is for both mobile phones and tablets. When running on phones the item is shown in a separate activity, when on a tablet a dual fragment layout is used and the item is shown on the right fragment.
In the main activity onCreate I check the intent and check if a item has been passed through and display it if it has. This is working fine on the phone but on a tablet the right fragment is not visible and hence the item can not be shown.
This is what I call at the end of onCreate (I had tried it in onStart and onResume)
Bundle data = queryIntent.getExtras();
if (data!=null){
Deal deal = data.getParcelable("notificationDeal");
if (deal!=null){
onDealSelected(deal);
}
}
The method onDealSeletced does the following
public void onDealSelected(Deal deal) {
if (!mDualFragments){
Intent showDealDetails = new Intent(getApplicationContext(), DealDetailsActivity.class);
showDealDetails.putExtra("Deal", deal);
showDealDetails.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(showDealDetails);
Log.d("OnDealSelected", "1");
}
else{ // must be tablet
if (dealDetailsFragment == null)
dealDetailsFragment = (DealDetailsFragment) getFragmentManager().findFragmentByTag("dealDetailsFragment");
if (!dealDetailsFragment.isVisible()){
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.right_fragment_container, dealDetailsFragment);
transaction.setTransitionStyle(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
transaction.commit();
getFragmentManager().executePendingTransactions(); // ensure it is done before we call update deal!
Log.d("OnDealSelected", "2");
}
if (dealDetailsFragment.isVisible()) {
dealDetailsFragment.updateDeal(deal);
Log.d("OnDealSelected", "3");
}
}
}
On a smartphone mDualFragments is false and hence it shows the deal in a new activity and works as expected.
When on a tablet it goes into the else, however it never gets into the final if as the fragment is not visible.
When running the application on a tablet it goes into the second if but after it the fragment is still not visible.
The same method is used at other points in the application (when a deal is not passed through in the intent) and has been working as expected.

You can use setArguments(Bundle bundle) to pass data to the fragment before it is attached (before the commit action). This way when the Fragment initializes itself it can call getArguments and parse the bundle. This way you don't have to worry about the fragment being visible yet, it can create its views when ready. There is a full example in the Fragment Docs

Try the transaction.add() method and hide the previous fragment. I suppose your fragment will be visible now.
transaction.add(R.id.right_fragment_container, dealDetailsFragment);

Related

Managing Fragments on Android Foldable

I am creating a new Android Project and soon android foldable devices will be launched. I have an Activity which has fragment called first fragment.
First Fragment has a button called first button which open second fragment which has a button called second and on click of second, third fragment opens.
Suppose user is in third fragment and user decides to unfold his device, will the user go back to fragment one or will he stay in fragment three. As far as I have understood from the Developer Summit, the activity will be destroyed and recreated when user unfolds his device so technically user goes backs to first fragment leading to poor user experience.
So my question is should I consider even using fragments?, If yes how to manage state so that user goes to the same fragment he was when he folds or unfolds his device.
Following is my code if I am changing fragments
private fun displayView(fragment: Fragment?, title: String) {
if (fragment != null) {
supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
supportFragmentManager.beginTransaction()
.replace(R.id.framelayout_activity_main, fragment, title).commit()
}
}
In onCreate(), you only want to execute a FragmentTransaction if this activity is being
newly created, instead of being recreated from a configuration change. Or, more accurately,
you only want to execute a FragmentTransaction if you do not already have fragments in the state that you want them.
So, a typical approach is to see if you already have a fragment in your container:
override fun onCreate(state: Bundle) {
super.onCreate(state)
if (supportFragmentManager.findFragmentById(R.id.framelayout_activity_main) == null) {
// do something to show your fragment
}
// other good stuff goes here
}
On the first onCreate() invocation, findFragmentById() will return null, so you execute your code to display your first fragment. On a subsequent onCreate() invocation after a configuration change, Android will have already set up your fragment(s) for you by the time onCreate() is called. So, in that case, findFragmentById() will return something other than null, so you know that you already have a fragment in your container and do not need to do anything more.

How to avoid multiple instances of fragments in Activity after app is killed and resumed?

I have an app with a Home screen that has 2 fragments (for now) and a navigation drawer. Currently I load the fragment A (Explore) on startup and load fragment B when clicked. From then on, I show and hide fragments. It's faster than recreating fragments on every click and my fragment A takes some time to load.
I've noticed that when I go to fragment B and go to another activity (let's call it activity 2) from there and leave the app and wait for it to be killed (or do something crazy like change the device language), and then come back to the same activity, it's still there. When I press back to go back to fragment B, sometimes (50% of times) the fragment B is drawn over fragment A. On clicking fragment A in the drawer, fragment A appears fine, but on clicking fragment B, there's another instance of fragment A and on top of that fragment B.
I've spent more than 2 days on this problem and got nowhere.
Here's my code for selecting the fragment:
private void selectItem(int position, boolean addExploreFragment) {
Log.d(tag, "selectItem: " + position);
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
//add explore fragment - this is called on app startup, but also when the app is killed and resumed which results in 2 explore fragments
if (addExploreFragment){
fragmentTransaction.replace(R.id.content_frame, mExploreFragment, EXPLORE_FRAGMENT_TAG);
Log.d(tag, "Replaced frame and added "+ mFragmentTags[position]);
} else {
//add fragment for the first time
if (getSupportFragmentManager().findFragmentByTag(mFragmentTags[position]) == null && position != 0) {
fragmentTransaction.add(R.id.content_frame, mFragments[position], mFragmentTags[position]);
Log.d(tag, "Added Fragment: "+ mFragmentTags[position]);
}
//shows and hides fragments
for (int i = 0; i < mFragments.length; i++) {
if (i == position) {
fragmentTransaction.show(mFragments[i]);
Log.d(tag, "Showing Fragment: "+ mFragmentTags[i]);
} else {
if (getSupportFragmentManager().findFragmentByTag(mFragmentTags[i]) != null) {
fragmentTransaction.hide(mFragments[i]);
Log.d(tag, "Hid Fragment: "+ mFragmentTags[i]);
}
}
}
}
fragmentTransaction.commit();
//not null check for calling selectItem(0) before loading the drawer
if (mDrawerList != null){
mDrawerList.setItemChecked(position, true);
}
}
I know for sure, the explore fragment is getting created twice and the two instances behave independently of each other (just sharing).
I'm lost what to do next. This is an issue which can be reproduced very easily on low end devices but on a device like Nexus 4 (my test device), the issue can be reproduced by changing the device language.
Has anyone got any ideas about this? Basically if the addExploreFragment block doesn't get called when there is already an exploreFragment, this issue could be solved, I think, but I've been unable to do so. Also, I tried removing all the fragments and then adding the exploreFragment but same thing happens (50% of times).
Thanks! and sorry for the long post, I felt I should share all the details.
Update: When I change the device language and come back to the app on Activity 2 and go back to Home activity, it has the fragment B open which is good, but fragment A get recreated because it's a heavy fragment and the system probably removed it from memory. Again, that's ok that it gets recreated IF it got removed by the system but why does it get recreated when it's not removed. I believe it's something with my code, on every 2nd attempt (without closing the app) this happens, 2 instances of the heavy fragment A. Out of ideas.
But shouldn't fragmentTransaction.replace remove all the previously added fragments and then add exploreFragment. It's not working like that. Neither fragment A nor Fragment B are getting removed.
I found out something new and rather odd to me. When you use fragmentTransaction.add, the listeners you have, like DrawerItemClickListener, on the previous fragment, are still active. And this is even if you use fragmentTransaction.commit.
So...I suspect when the add method is used, you actually clicked on another hidden button or hidden UI that has an event listener on the previous fragment. I don't like this of course and the effect may be very confusing. Yes, this happened to me and I didn't understand why for a while.
For now, I think the easiest code fix would be to use the replace method instead of add. The replace() makes listeners inactive. If it works, then you can make a better/elegant fix.
Let me know what happens....
I started to notice your post
when I go to fragment B and go to another activity
When you interact or start another Activity, you start a new set of Fragments. Look at this Google webpage # Fragments Lifecycle.
For clarification of my claim, there is a quote saying
A fragment must always be embedded in an activity and the fragment's
lifecycle is directly affected by the host activity's lifecycle.
You might as well read few paragraphs of it, at least.
I am not sure what your solution should be. Perhaps make the fragments distinctive, different and clear between the two Activities you have.

Using Fragments before they are attached

I am creating a master/detail type application. The Master view is a MasterFragment that shows a list of Master items, while the Detail view is a DetailsFragment that shows a list of Detail items.
When a user clicks a Master item in the list, I create a new DetailsFragment and show it using a transaction.
The details shown in a DetailFragment take some time to load (seconds) so I want to load them in a background thread and show the list once the loading is finished.
I now want to give the user the option to long-click a Master item, which (instead of opening it immediately and letting him wait) will create the DetailsFragment in the background (not visible yet), allowing him to browse the MasterFragment while it's loading. A navigation item is added to the Navigation Drawer so he can navigate to the DetailsFragment after some time when it has finished loading.
Think of it like using a web browser on very slow internet - instead of opening a page and waiting for it to load it is much nicer to open a page in a new tab in the background, browsing the current page some more while it loads, and then going back to the new tab when you think it must be finished loading. That's what I want to do in my app as well except with Fragments.
Now I learned that with creating Fragments it's important to use a static factory method that creates the Fragment, adds any objects as arguments to a Bundle, and then leave only an empty constructor.
public class DetailsFragment : Fragment
{
public DetailsFragment()
{
// Leave empty
}
public static DetailsFragment create(int masterId)
{
DetailsFragment f = new DetailsFragment();
Bundle args = new Bundle();
bundle.putInt("MasterId", masterId);
f.setArguments(args);
return f;
}
#Override
private void onCreate(Bundle bundle)
{
super.onCreate(bundle);
// Get master ID
int masterId = getArguments().getInt("MasterId");
// Load details in background thread
load(masterId);
}
#Background
private void load(int masterId)
{
//... (loading takes a few seconds...)
loadFinished();
}
#UiThread
private void loadFinished()
{
// update view...
}
}
(Note: I am using Android Annotations so that the 'load' method (with the #Background annotation) is run in the background. Just pretend I start it using a runner or AsyncTask or whatever.)
There is a problem here however: onCreate is not called until the Fragment is 'called upon', in other words there is no loading being done until the user opens the details fragment. I have tried onAttach instead of onCreate but the same thing happens. It seems onAttach is the first method called in the lifecycle and that is already too late.
I want the loading to start immediately, even if the Fragment is not shown yet (it may never be shown if the user doesn't navigate to it anymore).
How can I implement this behavior?
This is how I preload a fragment. Not sure it is the best way or the android way but it's the way I figured out how to do it.
In my layout xml I have the following:
<FrameLayout
android:id="#+id/master_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible" />
<FrameLayout
android:id="#+id/details_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible"/>
Notice that the "details_frame" is invisible. Then when you want to preload the fragment , you replace the invisible details_frame with the fragment and it will remain invisible:
getSupportFragmentManager().beginTransaction()
.replace(R.id.details_frame, <DetailsFragment>, <FragmentName>)
.commit();
Then once you want to display it you change the visibility to visible instead of invisible.
findViewById(R.id.details_frame).setVisibilility(View.VISIBLE);

Fragments, Parameters, and ActionBar 'Up' Navigation

So I am trying to get some experience with Fragments, but I'm finding some roadblocks.
My current situation is as follows.
I have an activity that displays a List whose content is determined by Extra Intent parameters sent from the 'calling' activity.
This List activity uses ListFragment declared in the XML like so:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" android:background="#color/black">
<fragment class="com.pixlworks.NLC.DirectoryBrowse$ListingFragment"
android:id="#+id/listing"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
Currently I get the parameter that indicates the type of content directly in the Fragment by accessing the Extra data of the Activity Intent (or saved Bundle if available):
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null)
mListingType = savedInstanceState.getString(Utils.DIRECTORY_TYPE_STORE_KEY);
else
mListingType = getActivity().getIntent().getStringExtra(Utils.DIRECTORY_TYPE_STORE_KEY);
// get content by type, create and set the adapter
}
Now part of my problem is that I am not sure this is the right way to 'pass' that parameter from the Activity to the Fragment.
On top of that, I am getting issues with this setup when using the Action Bar's UP Navigation. When I click on an item in this List Activity it goes to another activity showing the details of the selected item. From this detail activity:
If I use the back button, the List Activity is brought back from the stack as usual and everything works fine.
If I use the ActionBar's UP (despite following steps here), it would seem that a new instance is created instead of using the one in the stack and this new instance obviously is not getting the Extra parameter in the Intent. Since I am expecting the value to exist in the saved Bundle or in the Intent, my app crashes in this situation.
So to boil things down, I am not sure which of these to follow and how to make them work properly with 'UP' navigation:
A) Hold the 'type' parameter in a field in the Activity and save it in the Activity's Bundle onSaveInstanceState. In which case I am not sure how to then pass the value to the Fragment. In this case I would just need to make sure that UP calls the existing instance of the Activity List
B) Continue with my current setup of saving the value in the Fragment instead of the Activity, but again, how to handle the UP navigation correctly?
I know it is kind of multiple things I am asking here at the same time, but they are all connected, so I hope that I can get some help on this.
Thanks for any help in advance!
The UP navigation makes more sense to be used within the same activity level. That is the intention of the codes that you followed in the developers page. Because you started a new activity, if you want to return to previous activity like the back button you will need to call finish() to destroy the details activity first.
As for passing data from activity to fragment, when you create a new instance of fragment, you can pass the data to it as bundle, for example:
// in fragment class
public static MyFragment newInstance(Bundle arg) {
MyFragment f = new MyFragment();
f.setArguments(arg);
return f;
}
When you create a new fragment, you can call:
// in activity
Bundle arg = new Bundle();
int info = ...;
arg.putInt("INFO",info);
...
MyFragment mFragment = MyFragment.newInstance(arg);
Finally, to get the data in fragment:
int info = getArguments().getInt("INFO");
...
Instead of directly calling MyFragment mFragment = new MyFragment() to instantiate the fragment, you should use a static method to instantiate it. This is to prevent some crashes which might happen if you rotate the screen and the framework complains that it couldn't find a public empty constructor.
UPDATE
To answer your questions:
1) Say you start from activity A -> activity B. Then in activity B you press the up button. By logic of use, the up button will not bring you back to activity A, because its intention is to navigate one level up,but still inside, activity B. To return to activity A, you need to call finish() to destroy activity B first.
2) If your fragment is created in xml, you still can set arguments. In your xml, you set an id for the fragment android:id="#+id/fragment_id", then
// in activity
FragmentManager fm = getSupportFragmentManager(); // or getFragmentManager() if you don't have backward compatibility
MyFragment mFragment = fm.findFragmentById(R.id.fragment_id);
Bundle arg = new Bundle();
// put data blah blah
mFragment.setArguments(arg);
Just make sure you set the arguments before you use the fragment.
Simply said, intent is used when you pass data between calling activities; bundle is used when you want to pass data from activity to fragment.

Android Fragments should I reuse 1 fragment or create new instances?

I am trying to learn Fragments in Android and from various examples I have found there seems to be different ways of doing it and I just wanted to get some advice as to which is the correct way, or at least under which circumstances one way should be used over another.
One example created a layout that contained a fragment and a FrameLayout. In the code, when an item is selected from the ListFragment a new Fragment is created (with some data it requires in the constructor) and the FrameLayout is replaced with this new Fragment (using FragmentTransaction.replace()).
Another example has a layout file that declares the 2 fragments side by side. Now in the code when the user selects an item from the list in one fragment a call is made to the other fragment to update the data (based on the selected item).
So I am just wondering if either of these methods is preferred over the other or if there are certain circumstances where one should be used?
EDIT: here is the code for each of the two methods I was referring to:
1:
mCurCheckPosition = index;
if (mDualPane) {
// We can display everything in-place with fragments, so update
// the list to highlight the selected item and show the data.
getListView().setItemChecked(index, true);
// Check what fragment is currently shown, replace if needed.
DetailsFragment details = (DetailsFragment)
getFragmentManager().findFragmentById(R.id.details);
if (details == null || details.getShownIndex() != index) {
// Make new fragment to show this selection.
details = DetailsFragment.newInstance(index);
// Execute a transaction, replacing any existing fragment
// with this one inside the frame.
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.details, details);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
}
} else {
// Otherwise we need to launch a new activity to display
// the dialog fragment with selected text.
Intent intent = new Intent();
intent.setClass(getActivity(), DetailsActivity.class);
intent.putExtra("index", index);
startActivity(intent);
}
2:
public void onListItemClick(ListView l, View v, int position, long id) {
String item = (String) getListAdapter().getItem(position);
DetailFragment fragment = (DetailFragment) getFragmentManager()
.findFragmentById(R.id.detailFragment);
if (fragment != null && fragment.isInLayout()) {
fragment.setText(item);
} else {
Intent intent = new Intent(getActivity().getApplicationContext(),
DetailActivity.class);
intent.putExtra("value", item);
startActivity(intent);
}
}
So I am just wondering if either of these methods is preferred over the other or if there are certain circumstances where one should be used?
If the actual fragment does not need to change (i.e., it is the same fragment class), I would have the activity call a method on that fragment rather than replace it (your scenario #2), assuming it exists. That's much less expensive at runtime, and it's probably simpler to code as well.
If, however, the fragment might need to be a different one (e.g., depending on what you click, there may be different fragments for different types of model objects represented in the list), then replacing the fragment will be needed (your scenario #1). You could optimize the case where the fragment happens for this event to be of the same class, though I'd focus first on getting it working just by replacing the fragment and worry about the optimization if/when you have the time and inclination.
I'm not a fan of your #2 code structurally, though. IMHO, fragments should not be talking with other fragments directly. My preferred pattern is for fragments to "stick to their knitting", focusing solely on things within their own widgets and models. For events that affect other parts of the UI (e.g., list click), have the fragment notify the activity (e.g., via a listener interface). The activity is the one that knows which fragments should be around, as it is the one that created them in the first place. The activity can then either talk to the other fragment (if it exists), create the other fragment (if there is room), or start up another activity. If you prefer your #2 approach, you are welcome to use it -- it's just not what I'd do in your circumstance.

Categories

Resources