I have the ID of a FrameLayout which hosts different fragments (identified by different tags) at any given time. I sometimes wish to remove (not replace) whatever fragment is currently in the FrameLayout container. Here is what I tried and did not work:
Fragment f = fm.findFragmentById(R.id.framelayout_container);
if (f != null) {
txn.remove(f);
}
txn.commit();
So, I'm passing the ID of the container and if anything comes back (I was expecting a fragment currently in the container) I want to remove it.
Problem is that when I call above again (another UI event like button click), the findFragmentById returns the exact same Fragment again, as if prior remove/commit didn't do anything.
Btw, replace() works fine -- I'm struggling with the remove() only.
After spending more time on above problem, I discovered that calling remove() doesn't actually remove the fragment from the FragmentManager (duh) -- i.e. subsequent calls to findFragmentById still return the original fragment. What remove() does to the fragment is it changes its "isAdded" state, specifically:
fragment.isAdded() returns false
fragment.isDetached() returns false
fragment.isInLayout() returns false
fragment.isHidden() returns false
(jeez, haven't they exhausted all verbs by now?)
I expected the fragment to be actually removed from the manager after calling remove(), sort of like removing a node from a DOM tree. Silly me.
Related
In my app, I'm using a Single Activity with multiple Fragments Architecture and I navigate between them using the Navigation Library. Within one of the fragments, I have multiple categories, each associated with an ID. When a category is clicked, I take the user to that respective category explainer screen with the code below.
val directions = MainNavGraphDirections.launchFragmentWithThisCategoryId(categoryId!!)
onRoute(AppRoute.Directions(directions))
The above code sends them to the explainer screen associated with the associated categoryId. All is well until this point, the right explainer screen gets launched based on the categoryId. Within this explainer screen, I have a deep-link with a tag chatbot://fragment/wizardintro that is supposed to let the main activity know the specific follow-up fragment to send the user to. I denote all the fragments that can receive this deep-link with the code below.
companion object{
const val DEEP_LINK = "chatbot://fragment/wizardintro"
}
In the MainActivity, I have a method that receives all the different deep linking intents and matches them to the tags that will launch the respective category fragment with the code below.
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
intent?.data?.toString().also { deepLink ->
when (deepLink) {
IntroductionFragment.DEEP_LINK ->{
val categoryId = intent?.getLongExtra("categoryId", 0L)
val directions = MainNavGraphDirections.actionGlobalGoalWizard(categoryId)
navController.popBackStack()
navController.navigate(directions)
}
}
Now my problem arises when I try to retrieve this categoryId in the Main Activity and pass it to the next fragment. I don't get anything and only the default Long gets passed along. I think the function override fun onNewIntent(intent: Intent?) { } in the MainActivity recieve any intent. To be clear, these intents are sent from the explainer fragment which is technically a fragment that loads a json. Within the json there is a "route": {"type": "route", "url": "chatbot:///fragment/wizardintro"
In the MainActivity, the onNewIntent functions receive these intents unpacks them with this line intent?.data?.toString().also ...then in the when statement picks a fragment that has a matching chatbot:///fragment/wizardintro
I said all this to say that the main activity doesn't actually gets the categoryId, it simply picks launching the neccessary fragment without actually having anything associated with the categoryId
This makes me think that the first clicked categoryId doesn't actually get passed to the MainActivity. Although, to me, this seems like it shouldn't be this hard to pass objects/data from a fragment to an activity. What am I missing? What can I read to educate myself more on this?
Thanks for your time and responses!
Since we already got to the conclusion that MainActivity is not getting categoryId, you just need to pass that categoryId with the deep-link.
However, there is no need for any communication from Fragment to Activity.
You could achieve the same result through communication only between Fragment to Fragment, and Activity to Fragment.
What you want to do is to look more closely on deep-links and android navigation in the AndroidDocs, click here.
As you can tell, there are different ways to go around this, starting with arguments for each fragment. Assigning categoryId as an argument to the Fragment would help you use the navigationController and navigate to the new Fragment, while passing the categoryId to it.
Now, I am aware that you also wish to launch it with a deep-link; there's also a good explanation on here. According to the docs, you can place arguments in deep-links in the following manner...
Placeholders in the form of {placeholder_name} match one or more characters. For example, http://www.example.com/users/{id} matches http://www.example.com/users/4. The Navigation component attempts to parse the placeholder values into appropriate types by matching placeholder names to the defined arguments that are defined for the deep link destination. If no argument with the same name is defined, a default String type is used for the argument value.
The navigation is something amazing and capable, you just need to be aware of everything it can actually do. You can even bind it to a BottomNavBar, with extremely minimal amount of code.
Try going over the AndroidDocs about it, and it'll grow on you for sure.
I have a Range Input that's taking in an array of AssignmentSearchTypes for the valueRange. My task is to conditionally limit the search types based on the user's role. In particular, we want to only allow certain roles to assign jobs directly to another user. I've done this successfully, but when I actually pull up the page in a browser the "User" SearchType is always included, even when it shouldn't be.
I've tried declaring an array that only contains the desired SearchType
return new AssignmentSearchType[] { AssignmentSearchType.TC_GROUP }
but for some reason the TC_USER element still appears first in the rendered drop-down, in addition to TC_GROUP. I've stepped through the project line by line in the debugger, but it hasn't proved useful thus far.
Any ideas, Guidewire folks?
I want to parse a 'pushed' data layer string. I intend to use it to track click events and setup the appropiate funnels in Google Analytics, it looks as follows: products.view.19|view recent product|19
The first part (products.view.19) is the unique page identifier.
The second part (view recent product) is the action.
The last part is (19) is the action identifier, this way actions may be grouped and compared more easily.
So I did the following, I first created a trigger (it fires when a link has the tag 'data-trackclick' in it) which pushes the data value to a variable (variable for datalayer). However, now I want to split that variable in to 3 new variables, as described above. I selected 'javascript macro' for this but somehow it returns 'undefined'. The macro looks as follows:
function() {
var data = {{TrackClickData}};
var pieces = data.split('|');
if (pieces[0].length()) {
return pieces[0];
} else {
return data;
}
}
Obviously this didnt work since it would only run on the initial load and not (like I thought) when the macro was requested, so it should somehow be fired on the 'click' and then set the variables accordingly.
Is this possible to do? Or do I really have to add the dataLayer.push() in script tags?
A few things:
.length() is wrong, the property for array length is .length without the ()
if it exists, pieces[0] is not an array, then .length would return the string length, see How do you check for an empty string in JavaScript? for more standard way of checking for empty strings
Is this possible to do? There's virtually nothing you can't do with GTM, since you can write JavaScript code, you can do whathever you code allows you to do, and splitting a string to use parts of it as variables is certainly within the realm of possibilities.
My advise is to make your code work outside GTM first (eg test it in the browser console), then once it's all working, port it to GTM.
I am trying to build an app based on http://www.androidbegin.com/tutorial/actionbarsherlock-side-menu-navigation-nested-viewpager-fragment-tabs-tutorial/
One my fragment tabs contains a google map, so it must be nested in another fragment. But I cannot retrieve its ID, so I cannot load the map. I used:
view = inflater.inflate(R.layout.fragment_section_map, container, false);
And I think the layout should be inflated since I used something similiar for another tab. However when I tried to find the fragment with:
SupportMapFragment fragment = ((SupportMapFragment) getFragmentManager().findFragmentById(R.id.map1));
It returns null. It tried and checked what getFragmentManager().getFragments() will give at this point, and its the list of the fragment tabs (tab1/tab2/tab3). When I tried the same command in a different app but having only one fragment nested , I can see the hierarchy of my fragments in the list (mainactivity/tab/map) and so I can load the map.
Any idea of what I could do? I tried also getChildFragmentManager().getFragments() and that returns null too.
Please excuse the long post. I was playing around with a simple app and wanted to save a custom object in a fragment across an orientation change. Previously within activities this used to be handled using the onRetainNonConfigurationInstance() / getLastNonConfigurationInstance() methods. Seeing as these methods are now deprecated, the documentation encourages the use of fragments and the setRetainInstance(boolean) method.
I went ahead and played around with this method and then noticed a strange difference in behaviour when it came to saving the state of the fragments across orientation change. First up, a very brief explanation of the app I was playing with:
Main Activity
Fragment A (First fragment shown on app launch)
This is a simple fragment with 3 EditText controls. Each one has an ID in the layout file. The fragment also includes a button which when selected replaces Fragment A with Fragment B and saves the transaction on the backstack.
Fragment B
This is a fragment with an empty layout. If back is pushed, Fragment A is restored from the backstack.
Scenarios
Scenario A - setRetainInstance(false):
App launches and fragment A is displayed.
I enter values into the EditText fields and select the button.
Fragment B is displayed. I change device orientation once and hit the back key.
Fragment A is displayed with the entered values (view state) intact.
Scenario A - setRetainInstance(true):
The same behaviour takes place as above
Scenario B - setRetainInstance(false):
App launches and fragment A is displayed.
I enter values into the EditText fields and select the button.
Fragment B is displayed. I change device orientation twice and hit the back key.
Fragment A is still displayed with the entered values (view state) intact.
Scenario B - setRetainInstance(true):
App launches and fragment A is displayed.
I enter values into the EditText fields and select the button.
Fragment B is displayed. I change device orientation twice and hit the back key.
Fragment A is displayed with empty EditText controls, i.e. none of the entered values (view state) still intact.
For some reason the use of setRetainInstance(true) interferes with the view state of fragment A (on the backstack) when the orientation changes more than once.
Possible Explanation
I started getting nervous about the use of setRetainInstance while not having a full understanding of what was going on, so I dug around in the support library source code to try figure it out. At a very high level, I think this may be what is going on with setRetainInstance(true):
Fragment A is displayed, button is pressed and Fragment A is replaced by Fragment B. As part of this process, the FragmentManager (FM) removes Fragment A and fragmentA.mRemoving flag is set to true.
Change orientation the first time. At this point the FM attempts to save all state of the fragments:
Parcelable saveAllState() {
...
if (f.mState > Fragment.INITIALIZING && fs.mSavedFragmentState == null) {
fs.mSavedFragmentState = saveFragmentBasicState(f);
Fragment A has a CREATED state and has a null saved state, so it qualifies to have its state saved.
The activity is destroyed as part of the orientation change. Long story short, Fragment A has its state changed to INITIALIZING.
The activity is recreated and an attempt is made to move the state of the fragments to CREATED. However, at this point there is a check in the FM moveToState() method:
if (f.mRemoving && newState > f.mState) {
// While removing a fragment, we can't change it to a higher state.
newState = f.mState;
}
Because fragmentA.mRemoving remains true from step 1 as the fragment was retained (not recreated), it does not have its state increased to CREATED but remains in the INITIALIZING state. Note that even if one presses the back key now, Fragment A will still have its state intact, as a result of its state being saved in step 2.
Change orientation for the 2nd time. Once again the FM attempts to save all state of the fragments:
Parcelable saveAllState() {
...
if (f.mState > Fragment.INITIALIZING && fs.mSavedFragmentState == null) {
fs.mSavedFragmentState = saveFragmentBasicState(f);
However, because Fragment A is in the INITIALIZING state it does not qualify to have its state saved. Hence, once orientation completes for the 2nd time, if the back key is pressed the state of Fragment A is no longer intact.
Questions
Is this behaviour expected? Perhaps this relates to the documentation discouraging the use of setRetainInstance and backstack fragments?
How should we deal with view state and the use of setRetainInstance? Perhaps my use case is incorrect, but I would be nervous using the setRetainInstance functionality with this difference in behaviour.
Once again, sorry for the long post. Feedback will be appreciated as always.