1 /*
<lambda>null2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package androidx.navigation
17 
18 import android.app.Activity
19 import android.view.View
20 import androidx.annotation.IdRes
21 import androidx.core.app.ActivityCompat
22 import androidx.savedstate.SavedState
23 import java.lang.ref.WeakReference
24 
25 public actual object Navigation {
26     /**
27      * Find a [NavController] given the id of a View and its containing [Activity]. This is a
28      * convenience wrapper around [findNavController].
29      *
30      * This method will locate the [NavController] associated with this view. This is automatically
31      * populated for the id of a [NavHost] and its children.
32      *
33      * @param activity The Activity hosting the view
34      * @param viewId The id of the view to search from
35      * @return the [NavController] associated with the view referenced by id
36      * @throws IllegalStateException if the given viewId does not correspond with a [NavHost] or is
37      *   not within a NavHost.
38      */
39     @JvmStatic
40     public fun findNavController(activity: Activity, @IdRes viewId: Int): NavController {
41         val view = ActivityCompat.requireViewById<View>(activity, viewId)
42         return findViewNavController(view)
43             ?: throw IllegalStateException(
44                 "Activity $activity does not have a NavController set on $viewId"
45             )
46     }
47 
48     /**
49      * Find a [NavController] given a local [View].
50      *
51      * This method will locate the [NavController] associated with this view. This is automatically
52      * populated for views that are managed by a [NavHost] and is intended for use by various
53      * [listener][android.view.View.OnClickListener] interfaces.
54      *
55      * @param view the view to search from
56      * @return the locally scoped [NavController] to the given view
57      * @throws IllegalStateException if the given view does not correspond with a [NavHost] or is
58      *   not within a NavHost.
59      */
60     @JvmStatic
61     public fun findNavController(view: View): NavController {
62         return findViewNavController(view)
63             ?: throw IllegalStateException("View $view does not have a NavController set")
64     }
65 
66     /**
67      * Create an [android.view.View.OnClickListener] for navigating to a destination. This supports
68      * both navigating via an [action][NavDestination.getAction] and directly navigating to a
69      * destination.
70      *
71      * @param resId an [action][NavDestination.getAction] id or a destination id to navigate to when
72      *   the view is clicked
73      * @param args arguments to pass to the final destination
74      * @return a new click listener for setting on an arbitrary view
75      */
76     @JvmStatic
77     @JvmOverloads
78     public fun createNavigateOnClickListener(
79         @IdRes resId: Int,
80         args: SavedState? = null
81     ): View.OnClickListener {
82         return View.OnClickListener { view -> findNavController(view).navigate(resId, args) }
83     }
84 
85     /**
86      * Create an [android.view.View.OnClickListener] for navigating to a destination via a generated
87      * [NavDirections].
88      *
89      * @param directions directions that describe this navigation operation
90      * @return a new click listener for setting on an arbitrary view
91      */
92     @JvmStatic
93     public fun createNavigateOnClickListener(directions: NavDirections): View.OnClickListener {
94         return View.OnClickListener { view -> findNavController(view).navigate(directions) }
95     }
96 
97     /**
98      * Associates a NavController with the given View, allowing developers to use
99      * [findNavController] and [findNavController] with that View or any of its children to retrieve
100      * the NavController.
101      *
102      * This is generally called for you by the hosting [NavHost].
103      *
104      * @param view View that should be associated with the given NavController
105      * @param controller The controller you wish to later retrieve via [findNavController]
106      */
107     @JvmStatic
108     public fun setViewNavController(view: View, controller: NavController?) {
109         view.setTag(R.id.nav_controller_view_tag, controller)
110     }
111 
112     /**
113      * Recurse up the view hierarchy, looking for the NavController
114      *
115      * @param view the view to search from
116      * @return the locally scoped [NavController] to the given view, if found
117      */
118     private fun findViewNavController(view: View): NavController? =
119         generateSequence(view) { it.parent as? View? }
120             .mapNotNull { getViewNavController(it) }
121             .firstOrNull()
122 
123     @Suppress("UNCHECKED_CAST")
124     private fun getViewNavController(view: View): NavController? {
125         val tag = view.getTag(R.id.nav_controller_view_tag)
126         var controller: NavController? = null
127         if (tag is WeakReference<*>) {
128             controller = (tag as WeakReference<NavController>).get()
129         } else if (tag is NavController) {
130             controller = tag
131         }
132         return controller
133     }
134 }
135