0

I am having a product listing page. The recyclerview is filled with data from the Firebase firestore. When I click on any product, it will go to the details page.

view.findNavController().navigate(R.id.action_nav_products_to_productDetailFragment, bundle)

But if I click the back button, I am calling popBackStack() and come back to previous screen and it getting reaload.

findNavController().popBackStack()

The same behavior is happening if I click the system back button too.

I checked some other Stackoverflow posts and followed some answers. But nothing helps.

The one which I tried was, in onCreateView I check for the adapter initialization and If already initialized, it will not set the adapter of recylerview. But after applied this logic, I came to know that even if I am not setting the adapter to recyclerview, it will automatically reload.

I checked the samples provided by the google documentation which is working fine. But I can see the only difference in my case is Firestore.

implementation 'com.google.firebase:firebase-firestore-ktx:22.1.2'
implementation 'com.google.firebase:firebase-database:19.7.0'
implementation 'com.google.firebase:firebase-storage-ktx:19.2.2'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation "androidx.navigation:navigation-fragment:$2.4.0-alpha01"
implementation "androidx.navigation:navigation-ui:$2.4.0-alpha01"
implementation "androidx.navigation:navigation-fragment-ktx:$2.4.0-alpha01"
implementation "androidx.navigation:navigation-ui-ktx:$2.4.0-alpha01"

I tried a stable version of navigation too. But same behavior.

Base fragment code

abstract class BaseFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater,
        parent: ViewGroup?,
        savedInstanseState: Bundle?
    ): View? {
        val view =  getFragmentView(inflater, parent, savedInstanseState)
        view.findViewById<Toolbar>(R.id.toolbar)?.let {
            it.title = getTitle()
            it.setNavigationOnClickListener { (activity as HomeActivity).openDrawer() }
        }
        return view
    }

    abstract fun getFragmentView(
        inflater: LayoutInflater,
        parent: ViewGroup?,
        savedInstanceState: Bundle?
    ): View

    abstract fun getTitle():String
}

List fragment code below

class ProductsFragment : BaseFragment() {

    private val productsViewModel: ProductsViewModel by activityViewModels()
    private val categoryViewModel: CategoryViewModel by activityViewModels()

    override fun getFragmentView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        setUpProductsList()
        return inflater.inflate(R.layout.fragment_products, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        addFab.setOnClickListener {
            findNavController().navigate(R.id.action_nav_products_to_addProductsFragment)
        }
        txtSearch.addTextChangedListener {
            productsViewModel.getProduct(txtSearch.text.toString())
        }
    }

    override fun getTitle(): String {
        return resources.getString(R.string.menu_products)
    }

    private fun setUpProductsList(){
        categoryViewModel.getCategories("").observe(this, Observer {categories->
            productsViewModel.getProduct("").observe(this, Observer {products->
                products.let {
                    val adapter = ProductListingAdapter( products, categories, requireActivity())
                    productRV.adapter = adapter
                    productRV.layoutManager = LinearLayoutManager(requireContext())
                }
            })
        })
    }
}

If you need more info, could you please comment. I will update the answer.

KIRAN K J
  • 632
  • 5
  • 28
  • 57
  • Can you please share code of your fragment where you are setting adapter. – Muhammad Zahab Jun 16 '21 at 04:23
  • I need to check code of the fragment class. Where you are setting data in adapter. – Muhammad Zahab Jun 16 '21 at 04:29
  • @MuhammadZahabAhmadKhan Updated the code – KIRAN K J Jun 16 '21 at 04:36
  • @MuhammadZahabAhmadKhan Update the code again with BaseFragment – KIRAN K J Jun 16 '21 at 04:42
  • Yeah I am following your post. [Here](https://stackoverflow.com/questions/54581071/fragments-destroyed-recreated-with-jetpacks-android-navigation-components/56436855) is the guy having same problem which you are facing. It might help. – Muhammad Zahab Jun 16 '21 at 04:45
  • In my case will it solve if (rootView == null) rootView = inflater?.inflate(layout,container,false) else (rootView?.getParent() as? ViewGroup)?.removeView(rootView) – KIRAN K J Jun 16 '21 at 04:54
  • We need to try. I think this thing would be enough. if(rootView == null) rootView = inflater?.inflate(layout,container,false) else return rootView – Muhammad Zahab Jun 16 '21 at 04:57
  • 1
    So what is actually your problem, losing your scroll position or something else? Where does your `categoryViewModel` or `productsViewModel` store that `LiveData` that caches your last returned data so that it is instantly available to your Fragment when it comes back from the back stack? – ianhanniballake Jun 16 '21 at 04:59
  • 1
    It also seems like how your code is set up (resetting the adapter every time your data changes) would already cause issues with you losing your scroll position every time your data changes. Is there a reason you're doing this part wrong instead of updating your adapter with new data and using `notifyDataSetChanged()`, etc.? – ianhanniballake Jun 16 '21 at 05:00
  • @ianhanniballake he is setting adapter in onCreateView, i don't think that setting value instead of notifyDataSetChanged() is causing the issue. OnCreate should not be called when he is returning from other fragment. OnResume should but he is not doing that part of setting adapter in onResume – Muhammad Zahab Jun 16 '21 at 05:05
  • @KIRANKJ did returning same reference of fragment resolved your problem? – Muhammad Zahab Jun 16 '21 at 05:06
  • @MuhammadZahabAhmadKhan lateinit var rootView: View ----------return if(this::rootView.isInitialized) rootView else { setUpProductsList() rootView = inflater.inflate(R.layout.fragment_products, container, false) rootView } solved my problem – KIRAN K J Jun 16 '21 at 17:06
  • @MuhammadZahabAhmadKhan Do you find any other way to resolve this problem? Because it seems a basic problem. Did I miss any lifecycle method? – KIRAN K J Jun 16 '21 at 17:12
  • @MuhammadZahabAhmadKhan I can see I changed observe(viewLifecycleOwner from observe(this. Otherwise it will not work – KIRAN K J Jun 17 '21 at 00:55
  • @MuhammadZahabAhmadKhan Do you get my point? – KIRAN K J Jun 18 '21 at 10:28

1 Answers1

0

onDestroyView() get called when going forward to detail screen or another screen so obviously onCreateView() get called when user coming back from another screen. View is recreated, content is added, finally view is inflated. So automatically first visible item should be the zeroth index.

Most of the developers facing these kind of issues.

How to Handle: -> We need to fetch the content from ViewModel/repository only when adapter does not have any content. Below are code relevant snippet, hope this is helpful.

  1. Adapter creation

    private var adapter: MyAdapter? = null

  2. Initialize the adapter from onCreate() by passing relevant things

    adapter= MyAdapter(this)

  3. Mostly contents are fetching from onViewCreated() of fragment. Hence we need to fetch and add to adapter only when there is no data in adapter.

    if (adapter?.getItems()?.isEmpty() == true) fetchData() // could be io operation

it's worked for me.

Navas pk
  • 331
  • 3
  • 17
  • At your 3rd step, how about the else statement? Still need to bind the current data in adapter to recyclerview. Then recyclerview always appears as the first time fetching data, doesn't it? – Thái Quốc Toàn Jun 01 '23 at 15:17