Deep linking from Notification - how to pass data back up through the backstack?

北战南征 提交于 2020-07-09 03:09:19

问题


In my app, the user can select a category, then select an item within that category to finally view the item details. The standard/forward flow is:

SelectCategoryFragment -> SelectItemFragment -> ViewItemDetailsFragment

On selecting a category, the selectedCatId is passed via a Bundle from SelectCategoryFragment to SelectItemFragment:

    NavController navController = Navigation.findNavController(v);
    Bundle args = new Bundle();
    args.putLong(SelectItemFragment.ARG_CATEGORY_ID, selectedCatId);
    navController.navigate(R.id.action_nav_categories_to_items, args);

SelectItemFragment will then use the getArguments().getLong(ARG_CATEGORY_ID) value to query and display the appropriate items from the selected category.

That works fine. But I am now trying to implement deep linking when the users taps on a Notification, jumping them straight to ViewItemDetailsFragment with a backstack that can take them up to SelectItemFragment, then SelectCategoryFragment.

My problem is that, as described, SelectItemFragment depends on the ARG_CATEGORY_ID argument being passed to it in order to retrieve/display its data. I've read up on deep linking and nested navigation graphs, but don't really know how to pass ARG_CATEGORY_ID with deep linking/backstacks.

Is there a tidy way I can pass data from ViewItemDetailsFragment to SelectItemFragment when the user presses back?


回答1:


TLDR: problem might be solved using nested graphs. Refer to this article for more details.

Longer answer

Let's define simple fragment FragmentRed, FragmentGreen and FragmentBlue who inflate simple layouts each with corresponding background color:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/holo_red_dark">

    <TextView
        android:id="@+id/textView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:gravity="center"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

And declare fragment classes as such:

class FragmentRed : Fragment() {

    private val args: FragmentRedArgs by navArgs()

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.red, container, false)
        view.findViewById<TextView>(R.id.textView).text = args.foo.toString()
        return view
    }
}

FragmentGreen and FragmentBlue are copy pasted, but substituted all color texts with corresponding color texts, i.e. FragmentRedArgs -> FragmentBlueArgs, R.layout.red -> R.layout.blue.

Let's declare main activity layout as such:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:id="@+id/content"
    android:layout_height="match_parent">

    <fragment
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/main_graph" />
</FrameLayout>

Where main_graph is:

<?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"
    android:id="@+id/main_graph"
    app:startDestination="@id/fragment_red">

    <fragment
        android:id="@+id/fragment_red"
        android:name="com.playground.FragmentRed">
        <argument
            android:name="foo"
            android:defaultValue="0"
            app:argType="integer" />
        <action
            android:id="@+id/action_fragment_red_to_fragment_green"
            app:destination="@id/fragment_green" />
    </fragment>

    <navigation
        android:id="@+id/secondLevel"
        app:startDestination="@id/fragment_green">

        <fragment
            android:id="@+id/fragment_green"
            android:name="com.playground.FragmentGreen">
            <argument
                android:name="bar"
                android:defaultValue="0"
                app:argType="integer" />
            <action
                android:id="@+id/action_fragment_green_to_fragment_blue"
                app:destination="@id/fragment_blue" />
        </fragment>

        <fragment
            android:id="@+id/fragment_blue"
            android:name="com.playground.FragmentBlue">
            <argument
                android:name="zar"
                android:defaultValue="0"
                app:argType="integer" />
        </fragment>
    </navigation>

</navigation>

Now inside MainActivity let's spawn a new notification, passing in arguments for each of fragments: "foo" (red) - 1, "bar" (green) - 2, "zar" (blue) - 3.

Our expectation is upon clicking on notification to open Blue screen with text 3, upon back click see Green screen with 2 and another click should bring Red screen with 1 on the screen:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val navController = findNavController(R.id.nav_host_fragment)
        val pendingIntent = navController.createDeepLink()
            .setGraph(R.navigation.main_graph)
            .setDestination(R.id.fragment_blue)
            .setArguments(bundleOf("foo" to 1, "bar" to 2, "zar" to 3))
            .createPendingIntent()

        createNotificationChannel() // outside of the scope of this answer
        val builder = NotificationCompat.Builder(this, "my_channel")
            .setContentTitle("title")
            .setContentText("content text")
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            .setContentIntent(pendingIntent)
            .setSmallIcon(R.drawable.android)
            .setAutoCancel(true)
            .setChannelId("channelId")

        with(NotificationManagerCompat.from(this)) {
            notify(100, builder.build())
        }
    }

}

Here's the actual behavior on device:



来源:https://stackoverflow.com/questions/62393685/deep-linking-from-notification-how-to-pass-data-back-up-through-the-backstack

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!