How to reuse a dynmically generated fragement? - android

In the main fragment of my activity, I generate more fragments dependent on a config file.
When the device is rotated, I want to reuse the former generated fragments instead of creating them again.
This is the creation method, called in onCreateView of the main fragment:
private fun initChildFragments(elementsContainer: LinearLayout) {
val transaction = childFragmentManager.beginTransaction()
for (element in config.elements) {
val id = element.id
// create new container for fragment
addElementContainer(elementsContainer, id)
// try to re-use old fragment
var fragment = childFragmentManager.findFragmentByTag(element.getTag())
if (fragment == null) {
// create new fragment (should only happen at first run)
fragment = TestFragment.newInstance()
}
// add fragment to our container.
transaction.replace(id, fragment, element.getTag()
}
transaction.commitNow()
}
private fun addElementContainer(container: LinearLayout, id: Int) {
val childContainer = FrameLayout(context)
childContainer.id = id
container.addView(childContainer)
// for test purposes to make the container visible
val textView = TextView(context)
textView.setText("ID: " + id)
container.addView(textView)
}
When I first start the app, everything works fine. When I rotate the device, the fragments where found by findFragmentByTag and added to the containers. The element containers are visible on the screen (through the text view), but the fragments are not visible.
If I re-create the fragments every time the device rotates, they appear on the screen again, but then I loose all my ViewModel data.

Related

How to use 2 Fragment Manager for same Activity

I came up with something new, trying to use 2 Fragment Manager for same activity.
So for that I did some customization with help of FragmentHostCallback<{Activity}> and FragmentConroller class.
val fmCallBack_1 = DemoHostCallBack(this, Handler(Looper.getMainLooper()),0)
val fmController_1 = FragmentController.createController(fmCallBack_1)
fmController_1.attachHost(null)
fragmentManager_1 = fmController_1.supportFragmentManager
val fmCallBack_2 = DemoHostCallBack(this, Handler(Looper.getMainLooper()),0)
val fmController_2 = FragmentController.createController(fmCallBack_2)
fmController_2.attachHost(null)
fragmentManager_2 = fmController_2.supportFragmentManager
I am getting 2 different hash code for both of them.
Now as per my design
It contain
2 cards
On top of card it contain 5 Tabs
Green box will display FragmentResult. I am using instance of this fragment in second card too
in same activity.
The Problem
I used supportFragmentManager with a ContainerView to use multiple instance of FragmentResult in first card. It worked and it shows different result on change of tab.
But when I tried to show some data over second card tabs, It is not rendering result in second card, else supportFragmentManager showing it in First Card which I don't want.
Then I thought to create 2 Fragment Manager for a single Activity and use 2 Container View one for each to perform add or replace Fragment Transaction. But now no Fragment instance is loading to any card.
I think issue is with 2 custom Fragment manager which is not attached to host i.e. activity. How to achieve this, I spent 3 days on this.
private fun showCard_1_FragmentContainer(tag:String,fragment: Fragment,containerId:Int){
val existFragment = fragmentManager_1.findFragmentByTag(tag)
if(existFragment == null){
fragmentManager_1.beginTransaction()
.addToBackStack(null)
.replace(containerId, fragment,tag)
.commit()
}else{
fragmentManager_1.beginTransaction()
.addToBackStack(null)
.replace(containerId, existFragment,tag)
.commit()
}
}
private fun showCard_2_FragmentContainer(tag:String,fragment: Fragment,containerId:Int){
val existFragment = fragmentManager_2.findFragmentByTag(tag)
if(existFragment == null){
fragmentManager_2.beginTransaction()
.addToBackStack(null)
.replace(containerId, fragment,tag)
.commit()
}else{
fragmentManager_2.beginTransaction()
.addToBackStack(null)
.replace(containerId, existFragment,tag)
.commit()
}
}
private fun launchFragments(tag:String,fragment: Fragment,cardType:String){
when(cardType){
MConstants.CARD_1-> {showCard_1_FragmentContainer(tag,fragment,R.id.containerOne)}
MConstants.CARD_2-> {showCard_2_FragmentContainer(tag,fragment,R.id.containerTwo)}
}
}
Please help me on this with any other approach you have.

Pass to fragment using Navigation from Activity

I have an activity with two fragments. Either fragment is located on container in activity. Also activity has toolbar with settings button. I should pass on settings fragment using Navigation by click on settings button, but i have problems with it, because findNavController(R.id.home_nav).navigate(R.id.action_second_fragment_to_settings_fragment)
works if we pass to settings screen and second fragment is active, but from first active will be crash.
What do i need to do for solving this problem? I used just actions in Navigation component for passing between fragment. May be there is useful solution for this task
if your navController in MainActivity, with this code you can access to navController and send data to fragment.
load new fragment :
val bundle = Bundle()
bundle.putString("id", id)
(activity as MainActivity).navController.navigate(R.id.orderDetailFragment, bundle)
for get data in onViewCreated in fragment :
arguments?.let {
if (it.getSerializable("id") != null) {
val id = it.getString("id")
}
}
i hope it's useful

Navigation Component: Find if a fragment is in the back stack

I'm trying to find if a fragment is already in the backStack of my NavHostFragment (so it automatically manages the fragment transactions and backstack), in order to pop back to it when the user selects that destination from my Side Menu, instead of adding another new fragment to the backstack.
Here's the catch: Many of my fragments are the same Class, let's call it ArticleListFragment, and their contentId param (a simple string id) changes what is being displayed in those fragments.
This means I cannot use nav_host_fragment.childFragmentManager.findFragmentById() since multiple of my fragments in the backstack have the same fragment id.
What I've tried so far is this
var foundIndex = -1
for (i in 0 until nav_host_fragment.childFragmentManager.backStackEntryCount) {
val currFragmentTag = nav_host_fragment.childFragmentManager.getBackStackEntryAt(i).name
val currFragmentId = nav_host_fragment.childFragmentManager.getBackStackEntryAt(i).id
//val currFragment = nav_host_fragment.childFragmentManager.findFragmentByTag(currFragmentTag) // always returns null
val currFragment = nav_host_fragment.childFragmentManager.findFragmentById(currFragmentId) // always returns null
// currentFragment is null so the check always fails
if (currFragment is ArticleListFragment && currFragment.contentId == "a value I need to check") {
foundIndex = i
break
}
}
Am I doing something wrong? Is there another way to check if a fragment, added by the Android Navigation Component, is already in the back stack?
Well, I had the same issue, i can not say that this is a perfect solution but you can get it with
findNavController()
.backStack
.firstOrNull { it.destination.id == ID_YOU_WANT_TO_CHECK }
backstack is actually restricted so the method needs to be Suppressed.
#SuppressLint("RestrictedApi")

Android 10 (Q) transition shared element view in Fragment with RecyclerView is being stucked at fixed position

I usually solve problems by myself but this time im really getting mad and can't find proper fix.
Scenario:
I have two fragments let's say A and B.
In Fragment A I'm populating RecyclerView (later only RV) from Rest API.
In Fragment B I have "detail view" with CollapsingToolbarLayout.
When I click on item in RV I'm opening Fragment B with transition and one shared element which is AppCompatImageView where I set local drawable. In Fragment B is image inside CollapsingToolbarLayout.
Shared element transition works in Fragment B - image is moved correctly. Transition also works when I click on back button and image is moving back on it's original position in RV.
But here comes a problem which i can't resolve. In both Fragments that particular image is being stuck on position and when I'm scrolled RV or CollapsingToolbarLayout View is not changed - in Fragment A image is not moving when scrolling RV and in Fragment B image is not hiding on collapse/expand changes.
Do anybody faced this issue because i don't and really don't understand that kind of behaviour. Never happened to me after years of development.
Here is screenshot of Fragment A after going back from Fragment B:
Here i am executing fragment transaction with transition:
fun replaceFragmentWithTransition(context: Context,
sharedElement: View,
fm: FragmentManager?,
layoutContainer: Int,
fragment: Fragment,
tag: String,
addToBackStack: Boolean = false) {
fragment.sharedElementEnterTransition = TransitionInflater.from(context).inflateTransition(R.transition.default_transition)
fragment.sharedElementReturnTransition = TransitionInflater.from(context).inflateTransition(R.transition.default_transition)
val ft = fm?.beginTransaction()
ft?.addSharedElement(sharedElement, sharedElement.transitionName)
if (addToBackStack) { ft?.addToBackStack(null) }
ft?.replace(layoutContainer, fragment, tag)
ft?.commit()
}
Here I'm setting return transition callback in Fragment A (called in onViewCreated):
setExitSharedElementCallback(object: SharedElementCallback() {
override fun onMapSharedElements(names: MutableList<String>?, sharedElements: MutableMap<String, View>?) {
Timber.d("onMapSharedElements")
val vh = recyclerView.findViewHolderForAdapterPosition(selectedPanelIndex)
if (vh != null && sharedElements != null && names != null) {
Timber.d("Size: ${sharedElements.size}")
sharedElements[names[0]] = vh.itemView.findViewById(R.id.imagePanel)
}
}
})
Same in Fragment B but enter transition (called in onViewCreated):
setEnterSharedElementCallback(object: SharedElementCallback() {
override fun onMapSharedElements(names: MutableList<String>?, sharedElements: MutableMap<String, View>?) {
Timber.d("onMapSharedElements")
if (names != null && sharedElements != null) {
Timber.d("Size: ${sharedElements.size}")
sharedElements[names[0]] = imagePanel
}
}
})
In Fragment B also I assign transitionName into ImageView in onViewCreated function:
imagePanel.transitionName = transitionName
And also setting dynamic transition name in RV adapter:
inner class MyViewModel(override val containerView: View) : RecyclerView.ViewHolder(containerView), LayoutContainer {
fun bind(item: Panel, callback: (Panel, View) -> Unit) {
imagePanel.transitionName = "${containerView.context.getString(R.string.text_transition_name_panel_img)}_$adapterPosition"
item.getDrawableFromType().takeIf { it > 0 }?.let {
imagePanel?.setImageDrawable(containerView.context.getDrawableCompat(it))
imagePanel.show()
} ?: imagePanel.hide()
textName?.text = item.name
containerView.onClick { callback(item, imagePanel) }
}
}
Device: Google Pixel 3, Android 10
Note that if i don't use transition callbacks return transition not working but issue with enter transition in Fragment B remains same.
I feel lost in this case. Any help will be appreciated. I tried many things. Thanks.
Update!:
Seems this issue is related to Android 10 only! I tried my old Xiaomi and it works. I created issue here so hope it will be solved. It's really annoying. I will keep updates in this one.
Fixed by adding this lib
implementation "androidx.transition:transition:1.3.0-beta01"
Version 1.3.0-beta01
October 9, 2019
New features
Improved the integration with Fragment 1.2.0-beta01 to ensure that the Fragment’s View is not destroyed before the transition completes and that transitions are cancelled at the appropriate time. (aosp/1119841)
Version 1.2.0
October 9, 2019
Important changes since version 1.1.0
This version should be used if you're targeting API level 29. Otherwise, some of the transitions will not work properly. Instead of the reflection calls, this version uses the new public methods added in API Level 29. It is a part of our restrictions on non-SDK interfaces effort.
Source: https://developer.android.com/jetpack/androidx/releases/transition#1.3.0-beta01

Xamarin embedded fragment in fragment

I'm having a heap of strange problems with fragments that contain fragments embedded at design time in the axml.
My question is when I've finished with the fragment that I've loaded into a View with the FragmentManager, and then removed also using the FragmentManager, are the implicitly loaded fragments automatically destroyed? If not how should I clean up the parent fragment so that the embedded fragments are also drestroyed. Also when the parent fragment is destroyed do I need to call View.RemoveAllViews() to remove the fragments layout?
This seems to work. Capture the fragments as they are loaded into a list and then remove them when the main fragment is unloaded later. ( Must Dispose() )
public partial class MainActivity : Activity
{
private LinearLayout LoaderLayout;
private readonly List<Fragment> ActiveFragments = new List<Fragment>();
public override void OnAttachFragment( Fragment fragment ) { ActiveFragments.Add( fragment ); }
private async void ClearLoadFrame()
{
if( LoaderLayout == null )
LoaderLayout = FindViewById<LinearLayout>( Resource.Id.loaderLayout );
var Transaction = FragmentManager.BeginTransaction();
foreach( var Frag in ActiveFragments )
{
Transaction.Remove( Frag );
Frag.Dispose();
}
Transaction.CommitAllowingStateLoss();
ActiveFragments.Clear();
var Completed = false;
RunOnUiThread( () =>
{
LoaderLayout.RemoveAllViews();
Completed = true;
} );
await Task.Run( () =>
{
while( !Completed )
Thread.Sleep(50);
});
}
I discovered that what I'm trying to do isn't allowed.
From this article
Note that one limitation is that nested (or child) fragments must be dynamically added at runtime to their parent fragment and cannot be statically added using the tag.

Categories

Resources