Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NavController no current navigation node after device rotation

I am making app, which supports different device orientations. Navigation is carried out by Android Jetpack's Navigation. App main screen for landscape orientation is present below. It is list wrapper fragment (it is NavHostFragment, it is added to activity's layout in fragment tag), wrapper includes list fragment (fragment) and details fragment (FrameLayout). Portrait orientation is similar (wrapper and list in it, details is accessible throw navigation).

App main screen

My problem is after I change device orientation I get exception

java.lang.IllegalStateException: no current navigation node

First version of my layout with mocked data worked fine, the error appears after I add ROOM to my app, new order and update order fragments. It is a pity, I cannot localize source of error more exact.

List wrapper code

class OrderListWrapperFragment : RxFragment() {

    private val disposable = CompositeDisposable()

    var selectedOrderId: Long = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val bundle = savedInstanceState ?: arguments
        bundle?.let {
            selectedOrderId = it.getLong(EXTRA_ORDER_ID)
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? = inflater.inflate(R.layout.orders__list_wrapper, container, false)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        initializeToolbar(toolbar, getString(R.string.orders_title), false)

        newOrderButton
            .clicks()
            .subscribe {
                findNavController()
                    .navigate(R.id.action_orderListWrapperFragment_to_orderNewFragment)
            }
            .addTo(disposable)

        childFragmentManager.registerFragmentLifecycleCallbacks(callback, false)
    }

    override fun onDestroyView() {
        super.onDestroyView()

        childFragmentManager.unregisterFragmentLifecycleCallbacks(callback)

        disposable.clear()
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)

        outState.putLong(EXTRA_ORDER_ID, selectedOrderId)
    }

    private val callback = object : FragmentManager.FragmentLifecycleCallbacks() {

        private val disposable = CompositeDisposable()

        override fun onFragmentResumed(fm: FragmentManager, fragment: Fragment) {
            super.onFragmentResumed(fm, fragment)

            if (fragment is OrderListFragment) {
                fragment
                    .selectedItemIdChanges
                    .subscribeBy(onNext = {
                        selectedOrderId = it
                        if (orderDetailsContainer != null) {
                            childFragmentManager.commit {
                                replace(
                                    R.id.orderDetailsContainer,
                                    OrderDetailsFragment.newInstance(it)
                                )
                            }
                        } else {
                            findNavController()
                                .navigate(
                                    R.id.action_orderListWrapperFragment_to_orderDetailsFragment,
                                    bundleOf(EXTRA_ORDER_ID to it)
                                )
                            selectedOrderId = 0
                        }
                    },
                        onError = {
                            Log.d("detailView", it.toString())
                        })
                    .addTo(disposable)

                val orderId = selectedOrderId
                if (orderId != 0L) {
                    if (orderDetailsContainer != null) {
                        childFragmentManager.commit {
                            replace(
                                R.id.orderDetailsContainer,
                                OrderDetailsFragment.newInstance(orderId)
                            )
                        }
                    } else {
                        findNavController()
                            .navigate(//exception throws here
                                R.id.action_orderListWrapperFragment_to_orderDetailsFragment,
                                bundleOf(EXTRA_ORDER_ID to orderId)
                            )
                        selectedOrderId = 0
                    }
                }
            }
        }

        override fun onFragmentPaused(fm: FragmentManager, fragment: Fragment) {
            super.onFragmentPaused(fm, fragment)

            if (fragment is OrderListFragment) {
                disposable.clear()
            }
        }
    }

    companion object {
        private const val EXTRA_ORDER_ID = "EXTRA_ORDER_ID"
    }
}

My navigation graph

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/navigation_graph"
    app:startDestination="@id/orderListWrapperFragment">

    <fragment
        android:id="@+id/orderListWrapperFragment"
        android:name="com.mtgshipping.app.orders.orderList.OrderListWrapperFragment"
        android:label="OrderListWrapperFragment"
        tools:layout="@layout/orders__list_wrapper">
        <action
            android:id="@+id/action_orderListWrapperFragment_to_orderDetailsFragment"
            app:destination="@id/orderDetailsFragment"/>
        <action
            android:id="@+id/action_orderListWrapperFragment_to_orderNewFragment"
            app:destination="@id/orderNewFragment"/>
        <action
            android:id="@+id/action_orderListWrapperFragment_to_orderUpdateFragment"
            app:destination="@id/orderUpdateFragment"/>
    </fragment>

    <fragment
        android:id="@+id/orderDetailsFragment"
        android:name="com.mtgshipping.app.orders.orderDetails.OrderDetailsFragment"
        android:label="OrderDetailsFragment"
        tools:layout="@layout/orders__order_details">
        <action
            android:id="@+id/action_orderDetailsFragment_to_orderUpdateFragment"
            app:destination="@id/orderUpdateFragment"/>
    </fragment>

    <fragment
        android:id="@+id/orderNewFragment"
        android:name="com.mtgshipping.app.orders.orderNew.OrderNewFragment"
        android:label="OrderNewFragment"
        tools:layout="@layout/orders__order_new">
        <action
            android:id="@+id/action_orderNewFragment_to_orderListWrapperFragment"
            app:destination="@id/orderListWrapperFragment"/>
    </fragment>

    <fragment
        android:id="@+id/orderUpdateFragment"
        android:name="com.mtgshipping.app.orders.orderUpdate.OrderUpdateFragment"
        android:label="OrderUpdateFragment"
        tools:layout="@layout/orders__order_update">
        <action
            android:id="@+id/action_orderUpdateFragment_to_orderListWrapperFragment"
            app:destination="@id/orderListWrapperFragment"/>
    </fragment>
</navigation>

I made some debug in NavController, it showed in line 746 NavDestination currentNode = mBackStack.isEmpty() ? mGraph : mBackStack.getLast().getDestination(); after device rotation mGraph is null, other private fields is also null. Maybe something prevents NavController to initialize properly. I can provide layouts and other code, if it will be need.

like image 960
QuarK Avatar asked Jan 29 '20 15:01

QuarK


2 Answers

Thanks to Slav's comment, he was right. I updated navigation module to 2.2.0 navigation_version = '2.2.0' in app's module build.gradle

implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"
implementation "androidx.navigation:navigation-ui-ktx:$navigation_version"

After doing this problem is no longer appears, looks like that was a bug in navigation.

like image 147
QuarK Avatar answered Sep 28 '22 08:09

QuarK


You can also fix it like this. In your host activity in manifest adding this atribute:

<activity android:name=".MainActivity"
        android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"

But the best way is change your dependencies for navigation from:

implementation "android.arch.navigation:navigation-fragment-ktx:$navigation_version"
implementation "android.arch.navigation:navigation-ui-ktx:$navigation_version"

to

implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
like image 40
JoséAntonio Campillo Avatar answered Sep 28 '22 09:09

JoséAntonio Campillo