Jetpack Navigation deeplink with query parameters - android

it looks like it isn't possible to process a deeplink with query parameters in the new Jetpack Navigation library. If you put the following to the navigation.xml:
<deepLink app:uri="scheme://host/path?query1={query_value}" /> then the deeplink does not open the fragment.
After some digging I found that the culprit is probably in the NavDeepLink when it transforms the url from xml to a Pattern regex. Looks like the problem is a question mark that is not excaped.
I wrote a test which fails:
#Test
fun test() {
val navDeepLink = NavDeepLink("scheme://host/path?query1={query_value}")
val deepLink = Uri.parse("scheme://host/path?query1=foo_bar")
assertEquals(true, navDeepLink.matches(deepLink))
}
To make the test pass all I have to do is to escape the ? as following:
#Test
fun test() {
val navDeepLink = NavDeepLink("scheme://host/path\\?query1={query_value}")
val deepLink = Uri.parse("scheme://host/path?query1=foo_bar")
assertEquals(true, navDeepLink.matches(deepLink))
}
Am I missing something really basic here to pass query values to my Fragment or is this not supported feature at the moment?

You need to add DeepLink Navigation to AndroidManifest.xml ( special Activity that handles the fragment) so when deeplink clicked your app can receive the DeepLink and pass it to that navigation and fragment & can read it as argument:
I'll put Kotlin codes here :
In your navigation file, your fragment that gonna handle the deeplink with arguements must be like this:
<fragment
android:id="#+id/menu"
android:name="ir.hamplus.fragments.MainFragment"
android:label="MainFragment">
<action android:id="#+id/action_menu_to_frg_messenger_main"
app:destination="#id/frg_messenger_main"/>
<deepLink app:uri="http://hamplus.ir/request/?key={key}&id={id}" />
<argument android:name="key" app:argType="string"/>
<argument android:name="id" app:argType="string"/>
</fragment>
read deeplink arguments in frasgment /Activity :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//Or in activity read the intent?.data
arguments?.let {
Log.i("TAG", "Argument=$arguments")
var key = it.getString("key")
Log.i("TAG", "key=$key")
var id = it.getString("id")
Log.i("TAG", "id=$id")
}
}
Also add the nav-graph on AndroidManifest.xml in related Activity :
<activity
android:name=".MainActivity"
android:theme="#style/AppTheme.NoActionBar" >
<nav-graph android:value="#navigation/main_navigation"/>
</activity>

package androidx.navigation
import android.net.Uri
import androidx.test.runner.AndroidJUnit4
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
#RunWith(AndroidJUnit4::class)
class NavTest {
#Test
fun test() {
val navDeepLink = NavDeepLink("scheme://host/path\\?query1={query_value1}&query2={query_value2}")
val deepLink = Uri.parse("scheme://host/path?query1=foo_bar&query2=baz")
val bundle = navDeepLink.getMatchingArguments(deepLink)!!
assertTrue(bundle.get("query_value1") == "foo_bar")
assertTrue(bundle.get("query_value2") == "baz")
}
}
In the end it looks like NavDeepLink treats non escaped as "?" match-zero-or-one quantifier. You need to escape it. In other words, we have a leak of non documented implementation detail.
It might be not related to the exactly this case, but there is some similar issues with escaping "&" with "\" when using add command.
The issue was also touched in the following channel.

Related

Navigating with safeArgs in Kotlin throws and error

I have a fragment that can be either navigated to via a bottom navBar without arguments or from a different fragment with arguments. The navigation itself works perfectly fine but as soon as I add my arguments it crashes.
I'm unsure how much code is needed, so sorry if it's not complete:
This is my navigation fragment:
<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"
app:startDestination="#id/homeFragment">
<fragment
android:id="#+id/homeFragment"
android:name="fh.wfp2.flatlife.ui.views.HomeFragment"
android:label="Flatlife"
tools:layout="#layout/home_fragment" />
<fragment
android:id="#+id/todoFragment"
android:name="fh.wfp2.flatlife.ui.views.TodoFragment"
android:label="Todos">
<action
android:id="#+id/action_todoFragment_to_addTodoFragment"
app:destination="#id/addTodoFragment" />
<argument
android:name="taskname"
app:argType="string" />
<argument
android:name="isImportant"
app:argType="boolean" />
</fragment>
<fragment
android:id="#+id/addTodoFragment"
android:name="fh.wfp2.flatlife.ui.views.AddTodoFragment"
android:label="AddTodoFragment">
<action
android:id="#+id/action_addTodoFragment_to_todoFragment"
app:destination="#id/todoFragment" />
</fragment>
</navigation>
This is my addTodoFragment:
class AddTodoFragment : Fragment(R.layout.add_task_fragment) {
private lateinit var binding: AddTaskFragmentBinding
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = AddTaskFragmentBinding.bind(view)
binding.bAddTodo.setOnClickListener {
if (binding.etAddTodo.text.isNotEmpty()) {
// viewModel.onAddTodoClicked(binding.etAddTodo.text.toString(),
binding.cbImportant.isChecked)
findNavController().navigate(
AddTodoFragmentDirections.actionAddTodoFragmentToTodoFragment(
binding.etAddTodo.text.toString(), binding.cbImportant.isChecked
)
)
} else {
Snackbar.make(it, "The task field can't be empty", Snackbar.LENGTH_SHORT).show()
}
}
}
}
This is how I'm trying to get the arguments in TodoFragment.
private val args: TodoFragmentArgs by navArgs()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
//setting binding and other onClickListeners but they work fine
//reading args
//this i thought would work because then args only get read if not null
args?.let { //debugger stops here and app crashes
if(args.taskname.isNotEmpty())
viewModel.onAddTodoClick(Todo(name = args.taskname, isImportant = args.isImportant))
}
/*second try which I took directly from developers.android but which doesn't make sense in my case i
think because the args could be null I guess
val todoName = args.taskname
val isImportant = args.isImportant
viewModel.onAddTodoClick(Todo(name = todoName, isImportant = isImportant))
*/
}
This is the error message i get:
I hope it is clear what I mean, otherwise, I'll update the question. It'll probably be something simple but I can't quite put my finger on it.
So credit to #Onik for the idea to leave SafeArgs away. I needed to dump the SafeArgs class call for receiving the arguments and it looks like this now. This is definitely not the cleanest way I guess but I don't know yet why the SafeArgs call doesn't work and I'm too new to Kotlin to write the following code in a cleaner way. But it works, for now.
arguments?.let {
var myArg1: String? = null
var myArg2: Boolean
arguments?.getString("argument1")?.let { arg1->
myArg1= arg1
}
arguments?.getBoolean("argument2")?.let { arg2->
myArg2= arg2
if (myArg1!= null)
//do logis with args
}
}
val args = arguments?.let {
SecondFragmentArgs.fromBundle(
it
)
}
if (args != null) {
firstDataList = args.taskName
secondDataList = args.isImportant
}
This is how the receiving fragment code should look like. This should work.

safeargs argument not found in NavDirections

I have implemented an argument to be passed between fragments in nav_graph, however when I attempt to set the argument in the originating fragment, the argument is not found by the NavDirections.
Note that Navigation works fine before trying to pass the argument.
If I do a Clean Project I lose the NavDirections. If I do a Rebuild I lose the argument.
Gradle:app
//Navigation
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
apply plugin: "androidx.navigation.safeargs.kotlin"
nav_graph.xml
<fragment
android:id="#+id/destination_home"
android:name="com.android.joncb.flightlogbook.HomeFragment"
android:label="#string/lblHome"
tools:layout="#layout/fragment_home">
<action
android:id="#+id/action_home_to_fltHistory"
app:destination="#id/destination_fltHistory" />
<action
android:id="#+id/action_home_to_stats"
app:destination="#id/destination_statistics" />
<action
android:id="#+id/action_home_to_newFlight"
app:destination="#id/destination_newFlight" />
<action
android:id="#+id/action_home_to_fltDetails"
app:destination="#id/destination_fltDetails" />
<argument
android:name="fltData"
app:argType="string" />
</fragment>
and in my Home Fragment I get the error "Unresolved reference: fltData"
card_nextFlight.setOnClickListener {
val actionDetails = HomeFragmentDirections.actionHomeToFltDetails()
actionDetails.fltData ( flightData.toString())
Navigation.findNavController(it).navigate(actionDetails)
}
flightData is a data class
data class FlightDTO(
var airlineName: String, var faCode: String, var fltNo: String, var aircraft: String,
var depAP: String, var arrAP: String, var schedDep: String, var schedArr: String,
var date: String, var leg: Int = 0, var actDep: String = "", var actArr: String = "" ){
...
override fun toString(): String {
return "$airlineName $faCode $fltNo $aircraft $depAP $schedDep $arrAP $schedDep $date"
}
}
I want to pass the class ideally by making the class Parcelable, but until I can pass a string, there is no point venturing down the parcel line.
You are writing your XML wrong, think like this : The way I structure my XML properties is the way the generated code will look like and received between destinations sort of...
So basically in your nav_graph.xml you should change to:
<fragment
android:id="#+id/destination_home"
android:name="com.android.joncb.flightlogbook.HomeFragment"
android:label="#string/lblHome"
tools:layout="#layout/fragment_home">
<action
android:id="#+id/action_home_to_fltHistory"
app:destination="#id/destination_fltHistory" />
<action
android:id="#+id/action_home_to_stats"
app:destination="#id/destination_statistics" />
<action
android:id="#+id/action_home_to_newFlight"
app:destination="#id/destination_newFlight" />
<action
android:id="#+id/action_home_to_fltDetails"
app:destination="#id/destination_fltDetails">
<argument
android:name="fltData"
app:argType="string" />
</action>
</fragment>
and in your destination it should look something like:
<fragment
android:id="#+id/destination_fltDetails"
android:name="com.android.joncb.flightlogbook.FlightDetailsFragment"
android:label="#string/lblFlightDetails"
tools:layout="#layout/fragment_flight_details">
<argument
android:name="fltData"
app:argType="string" />
</fragment>
and in your flight details fragment the properties are received by using:
private val args: FlightDetailsFragmentArgs by navArgs()
println(args.fltData) // prints the navigation data
UPDATE:
Forgot to mention your OnClickListener in your Home fragment that would look more like this:
card_nextFlight.setOnClickListener {
val actionDetails = HomeFragmentDirections.actionHomeToFltDetails(flightData.toString())
Navigation.findNavController(it).navigate(actionDetails)
}
For my case, I wrote a buggy code like that -
NavController navController = NavHostFragment.findNavController(this);
NavDirections navDirections = MyDestinationFragmentDirections.actionMyAction(myArgumentValue);
navController.navigate(navDirections.getActionId());
Then I change the last line into this -
navController.navigate(navDirections);
And finally,it worked as expected!!!
The logic behind this was, in NavController class the method which accepting int (resId of action) always put null argument -
public void navigate(#IdRes int resId) {
navigate(resId, null);
}
So we should use -
public void navigate(#NonNull NavDirections directions) {
navigate(directions.getActionId(), directions.getArguments());
}
method if we are willing to pass an arguments via an action.
my mistake was the following. I had something like
NavDirections action =
SpecifyAmountFragmentDirections
.actionSpecifyAmountFragmentToConfirmationFragment();
I changed to something like
ConfirmationAction action =
SpecifyAmountFragmentDirections
.actionSpecifyAmountFragmentToConfirmationFragment();
Rather than pass a data class, I have created a JSON String and passed a string
card_nextFlight.setOnClickListener {
val dataString = flightData.toJSONString()
val actionDetails = HomeFragmentDirections.actionHomeToFltDetails(dataString)
Navigation.findNavController(it).navigate(actionDetails)
}
To get this to work I had to modify the actionHomeToFltDetails function to receive a string in HomeFragmentsDirections
fun actionHomeToFltDetails(fltData: String): NavDirections = ActionHomeToFltDetails(fltData)
}
I could not get #Lucho approach to handle the arg in the destination fragment to work so reverted to bundle management, and converted the JSON string back to a data class
const val ARG_PARAM1 = "fltData"
.
.
.
arguments?.let {
argFltData = it.getString(ARG_PARAM1)
Log.e("args","Passed Argument: $argFltData")
fltData = gson.fromJson(argFltData, FlightDTO::class.java)
}
Thanks again for your input and I hope this helps someone else through the drama.

Android Navigation Component : Pass value (arguments) in fragments

What I have done:
I have created Navigation Drawer Activity, As updated new format of Navigation Drawer Activity, As per new Android architecture, I got it with Navigation Component structure.
The NavigationView code with NavController and NavigationUI is below which is opening fragment when I click on any navigation item.
DrawerLayout drawer = findViewById(R.id.drawer_layout);
NavigationView navigationView = findViewById(R.id.nav_view);
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
mAppBarConfiguration = new AppBarConfiguration.Builder(
R.id.nav_home, R.id.nav_profile, R.id.nav_privacy_policy,
R.id.nav_terms, R.id.nav_contact_us, R.id.nav_share, R.id.nav_send)
.setDrawerLayout(drawer)
.build();
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);
NavigationUI.setupWithNavController(navigationView, navController);
This is for nav_host_fragment:
<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:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="#navigation/mobile_navigation" />
The navigation is happening using this navigation/mobile_navigation.xml
<?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/mobile_navigation"
app:startDestination="#+id/nav_home">
<fragment
android:id="#+id/nav_home"
android:name="com.sohamerp.marsremedies.fragment.HomeFragment"
android:label="#string/menu_home"
tools:layout="#layout/fragment_home" />
<fragment
android:id="#+id/nav_profile"
android:name="com.sohamerp.marsremedies.fragment.ProfileFragment"
android:label="#string/menu_my_profile"
tools:layout="#layout/fragment_profile" />
<fragment
android:id="#+id/nav_privacy_policy"
android:name="com.sohamerp.marsremedies.fragment.PrivacyPolicyFragment"
android:label="#string/menu_privacy_policy"
tools:layout="#layout/fragment_privacy_policy" />
<fragment
android:id="#+id/nav_terms"
android:name="com.sohamerp.marsremedies.fragment.TermsConditionFragment"
android:label="#string/menu_terms"
tools:layout="#layout/fragment_terms_condition" />
<fragment
android:id="#+id/nav_contact_us"
android:name="com.sohamerp.marsremedies.fragment.ContactUsFragment"
android:label="#string/menu_contact_us"
tools:layout="#layout/fragment_terms_condition" />
</navigation>
What I want to do:
Now I want to pass some values as a bundle (arguments) in Fragment when it's called.
Scenario: I have two fragments PrivacyPolicyFragment and TermsConditionsFragment, In both fragments, I am just opening links inside WebView accordingly. So When I click on the menu item of Privacy Policy, I will pass a link related to the same.
In this new structure navigation/mobile_navigation.xml opening fragments, How can I pass arguments?
Any help?
So I forgot to go through this link : Define Destination Arguments
But this answer helpful to all lazy peoples like me:
Add dependency in project level build.gradle:
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.1.0"
Apply plugin in app level build.gradle:
apply plugin: "androidx.navigation.safeargs"
Using XML: predefined (static) value:
In xml file of navigation /navigation/mobile_navigation.xml declare argument tag as below or you can design through this link:
<fragment
android:id="#+id/nav_privacy_policy"
android:name="com.sohamerp.marsremedies.fragment.PrivacyPolicyFragment"
android:label="#string/menu_privacy_policy"
tools:layout="#layout/fragment_privacy_policy" >
<argument
android:name="privacyPolicyLink"
app:argType="string"
android:defaultValue="http://sohamerp.com/avo/avo_privacy_policy.html"/>
</fragment>
<fragment
android:id="#+id/nav_terms"
android:name="com.sohamerp.marsremedies.fragment.PrivacyPolicyFragment"
android:label="#string/menu_terms"
tools:layout="#layout/fragment_terms_condition" >
<argument
android:name="privacyPolicyLink"
app:argType="string"
android:defaultValue="http://sohamerp.com/avo/avo_privacy_policy.html"/>
</fragment>
Now you have to write code in your Fragment like:
if(getArguments() != null) {
// The getPrivacyPolicyLink() method will be created automatically.
String url = PrivacyPolicyFragmentArgs.fromBundle(getArguments()).getPrivacyPolicyLink();
}
Hope it will helps you others.
Simple and fast solution:
pass arguments between destinations
Bundle bundle = new Bundle();
bundle.putString("amount", amount);
Navigation.findNavController(view).navigate(R.id.confirmationAction, bundle);
and receiving
TextView tv = view.findViewById(R.id.textViewAmount);
tv.setText(getArguments().getString("amount"));
In this scenario, you can use
private NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
// Create the Bundle to pass, you can put String, Integer, or serializable object
Bundle bundle = new Bundle();
bundle.putString("link","http://yourlink.com/policy");
bundle.putSerializable("USER", user); // Serializable Object
navController.navigate(R.id.nav_terms, bundle); // called fragment with agruments
In case of any help you can reply on it
To pass arguments to other Fragments/Destinations, use Safe Args which ensures type safety. Just like #bromden illustrated, Safe Args will generate a class for each fragment/destination where an action originates. You can then pass the arguments into the action that navigates to the Fragments.
In the receiving fragment, say PrivacyFragment if your code is in Kotlin, use by navArgs() property delegate to access the arguments. i.e.
val args: PrivacyFragmentArgs by navArgs()
To better understand this, visit Pass data between destinations
In newer version of Android Studio 3.2+, below dependency and plug-in need to add in both build.gradle file
Step-1
Add dependency in Project-Level build.gradle
dependencies {
classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5'
}
Apply plugins in App-Level build.gradle
plugins {
id 'androidx.navigation.safeargs'
}
Step-2
In Navigation file, res/navigation/nav_graph.xml
Declare argument tag in any fragment or inner fragment with action tag
List item
Sample xml code
<fragment
android:id="#+id/nav_register"
android:name="com.pd.demo.ui.profile.RegisterFragment"
android:label="#string/title_register"
tools:layout="#layout/fragment_register">
<action
android:id="#+id/action_nav_register_to_nav_verify_otp"
app:destination="#id/nav_verify_otp">
<argument
android:name="mobile"
app:argType="string" />
<argument
android:name="password"
app:argType="string" />
</action>
</fragment>
Step-3
Below Kotlin code, pass argument to destination fragment
val bundle = bundleOf("mobile" to binding.etMobileNo.text.toString().trim())
Navigation.findNavController(binding.root).navigate(R.id.action_nav_register_to_nav_verify_otp, bundle)
Step-4
Below Kotlin code, get bundle argument from source fragment
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
mobileNo = arguments!!.getString("mobile").toString()
password = arguments!!.getString("password").toString()
}
This code will helps
You could implement NavigationView.OnNavigationItemSelectedListener
And do something like this:
override fun onNavigationItemSelected(item: MenuItem): Boolean {
drawer_layout.closeDrawers()
if (item.itemId == nv_navigation_drawer_navigation_view.checkedItem?.itemId)
return false
Handler().postDelayed({
when (item.itemId) {
R.id.nav_privacy_policy -> {
val action = FragmentDirections.actionFragmentToPrivacyFragment("Policy link")
findNavController().navigate(action)
}
}
}, DRAWER_NAVIGATION_DELAY)
return true
}
And in xml you can add argument to the recieving fragment, in this case
<fragment
android:id="#+id/nav_privacy_policy"
android:name=".fragment.PrivacyPolicyFragment"
android:label="#string/menu_privacy_policy"
tools:layout="#layout/fragment_privacy_policy">
<argument
android:name="policy"
app:argType="string" />
</fragment>
You can also pass serializable objects, enum values and arrays of primitive types.
For example:
enum class ObjectType : Serializable {
FIRST, SECOND
}
Then, add arguments to the xml
<fragment
android:id="#+id/nav_profile"
android:name="com.sohamerp.marsremedies.fragment.ProfileFragment"
android:label="#string/menu_my_profile"
tools:layout="#layout/fragment_profile" >
<argument
android:name="myObjectType"
android:defaultValue="SECOND"
app:argType="com.project.app.data.ObjectType" />
</fragment>
Note, that you should specify complete path!
Passing data from the start destination with NavController NavGraph navigate is straightforward. I use this to display order lines associated to an order header:
private void showRepositionLinesFragment(AppObjects.RepOrderHeader orderHeader) {
int number = orderHeader.getOrderNumber();
String orderNumber = String.format("%06d",number);
String createDate = orderHeader.getCreateDate();
Globals.LogTrace(this, AppAlertDialog.DialogType.Info,
"Navigate to FragRepoLines with orderNumber: " + orderNumber,false);
NavController navController = NavHostFragment.findNavController(FragmentRepositionHeaders.this);
Bundle bundle = new Bundle();
bundle.putString(getString(R.string.arg_header_ordernumber),orderNumber);
bundle.putString(getString(R.string.arg_repheader_createdate), createDate);
navController.getGraph().findNode(R.id.FragRepoLines).setLabel(orderNumber + " " + createDate);
navController.navigate(R.id.action_FragRepoHeaders_to_FragRepoLines,bundle);
}
Getting data from the fragment that handles the order lines turned to be more complicated. Tried for hours with NavController getArguments().
In the end this is what worked for me.
In the start fragment:
NavController navController = NavHostFragment.findNavController(this);
// We use a String here, but any type that can be put in a Bundle is supported
MutableLiveData<String> liveData = navController.getCurrentBackStackEntry()
.getSavedStateHandle()
.getLiveData(getString(R.string.arg_header_ordernumber));
liveData.observe(getViewLifecycleOwner(), new Observer<String>() {
#Override
public void onChanged(String s) {
Globals.LogTrace(this, AppAlertDialog.DialogType.Info, "+++++++++ liveData changed -> " + s, false);
}
});
In the destination fragment:
String arg = getString(R.string.arg_header_ordernumber);
NavController navController = NavHostFragment.findNavController(this);
NavBackStackEntry navBackStackEntry = navController.getCurrentBackStackEntry();
if (navBackStackEntry != null) {
SavedStateHandle savedStateHandle = navBackStackEntry.getSavedStateHandle();
if (savedStateHandle != null) {
savedStateHandle.set(arg, "000000");
} else {
Globals.LogTrace(this, AppAlertDialog.DialogType.Info,"savedStateHandle == null",false);
}
} else {
Globals.LogTrace(this, AppAlertDialog.DialogType.Info,"navBackStackEntry == null",false);
}
Source: Interact programmatically with the Navigation component
I changed the navController.getPreviousBackStackEntry() for navController.getCurrentBackStackEntry()
I had the same issue but I´m still not able to pass the arguments using fragment directions. Since I need the value in several of my fragments I decided to use a companion object in my main activity. It´s probably not the best but it solves the problem:
class MainActivity : AppCompatActivity() {
companion object{
var myGlobalVar = "Example"
}
override fun onCreate(savedInstanceState: Bundle?) {....
Then I can access its value in all of my fragments by importing it:
import myAppPackage.MainActivity.Companion.myGlobalVar
I had to delete the argument from my navGraph but i can still access it in the background.

Navigate to fragment on FAB click (Navigation Architecture Components)

I have no idea how to, using the new navigation architecture component, navigate from my main screen (with a FloatingActionButton attatched to a BottomAppBar) to another screen without the app bar.
When I click the fab I want my next screen (fragment?) to slide in from the right. The problem is where do I put my BottomAppBar? If I put it in my MainActivity then I have the issue of the FloatingActionButton not having a NavController set. I also cannot put my BottomAppBar in my Fragment. I am at a loss.
Ran into this issue today and I found out that there is a simple and elegant solution for it.
val navController = findNavController(R.id.navHostFragment)
fabAdd.setOnClickListener {
navController.navigate(R.id.yourFragment)
}
This takes care of the navigation. Then you must control the visibility of your BottomAppBar inside your Activity.
You could have your BottomAppBar in MainActivity and access your FloatingActionButton in your fragment as follows
activity?.fab?.setOnClickListener {
/*...*/
findNavController().navigate(R.id.action_firstFragment_to_secondFragment, mDataBundle)
}
You could hide the BottomAppBar from another activity as follows
(activity as AppCompatActivity).supportActionBar?.hide()
Make sure you .show() the BottomAppBar while returning to previous fragment
Put it in MainActivity and setOnClickListener in onStart() of the activity and it will work fine.
override fun onStart() {
super.onStart()
floatingActionButton.setOnClickListener {
it.findNavController().navigate(R.id.yourFragment)
}
}
Note:This solution is like and hack and better is to follow Activity LifeCycle and setUp OnClickListener when the activity is ready to interact.
Similar question [SOLVED]
if you wanted to navigate to certain fragment (not the star one) in the beginning for some reason, and also you have to graphs for one activity, here is what I suggest:
this method will start activity
companion object {
const val REQUEST_OR_CONFIRM = "request_or_confirm"
const val IS_JUST_VIEW = "IS_JUST_VIEW"
const val MODEL = "model"
fun open(activity: Activity, isRequestOrConfirm: Boolean, isJustView: Boolean = false, model: DataModel? = null) {
val intent = Intent(activity, HostActivity::class.java)
intent.putExtra(REQUEST_OR_CONFIRM, isRequestOrConfirm)
intent.putExtra(IS_JUST_VIEW, isJustView)
intent.putExtra(MODEL, model)
activity.startActivity(intent)
}
}
and then in, onCreate method of Host Activity, first decide which graph to use and then pass the intent extras bundle so the start fragment can decide what to do:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_purchase_nav)
if (intent.getBooleanExtra(REQUEST_OR_CONFIRM, true)) {
findNavController(R.id.nav_host_fragment).setGraph(R.navigation.nav_first_scenario, intent.extras)
} else {
findNavController(R.id.nav_host_fragment).setGraph(R.navigation.nav_second_scenario, intent.extras)
}
}
and here's how you can decide what to do in start fragment:
if (arguments != null && arguments!!.getBoolean(HostActivity.IS_JUST_VIEW)){
navigateToYourDestinationFrag(arguments!!.getParcelable<DataModel>(HostActivity.MODEL))
}
and then navigate like you would do normally:
private fun navigateToYourDestinationFrag(model: DataModel) {
val action = StartFragmentDirections.actionStartFragmentToOtherFragment(model)
findNavController().navigate(action)
}
here's how your graph might look in case you wanted to jump to the third fragment in the beginning
PS: make sure you will handle back button on the third fragment, here's a solution
UPDATE:
as EpicPandaForce mentioned, you can also start activities using Navigation Components:
to do that, first add the Activity to your existing graph, either by the + icon (which didn't work for me) or by manually adding in the xml:
<activity
android:id="#+id/secondActivity"
tools:layout="#layout/activity_second"
android:name="com.amin.SecondActivity" >
</activity>
you can also add arguments and use them just like you would in a fragment, with navArgs()
<activity
android:id="#+id/secondActivity"
tools:layout="#layout/activity_second"
android:name="com.amin.SecondActivity" >
<argument
android:name="testArgument"
app:argType="string"
android:defaultValue="helloWorld" />
</activity>
in koltin,here's how you would use the argument, First declare args with the type of generated class named after you activity, in this case SecondActivityArgs in top of your activity class:
val args: SecondActivityArgsby by navArgs()
and then you can use it like this:
print(args.testArgument)
This doesn't destroy BottomAppBar. Add this to MainActivity only and don't do anything else
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
fabAdd.setOnClickListener {
findNavController(navHostFragment).navigate(R.id.fab)
}

How to pass object of type Parcelable to a Fragment using Navigation type safeargs plugin

I am rewriting my simple UI app to use Navigation architecture component, I need to pass a Pojo that implements Parcelable, have not seen any doc on how to do that.
Any help would be appreciated.
Since safe-args-gradle-plugin:1.0.0-alpha03 you can use Parcelable objects by using their fully qualified class name:
<argument
android:name="item"
app:argType="com.example.app.model.Item"/>
Parcelable arguments are now supported, using a fully qualified class name for app:type. The only default value supported is "#null" (https://issuetracker.google.com/issues/79563966)
Source: https://developer.android.com/jetpack/docs/release-notes
To support nullability one has to use android:defaultValue="#null" with app:nullable="true".
I know the answer is already there but this may help someone. Code snippet
In build.gradle add this dependancy
ext{
...
navigation_version = '1.0.0-alpha11'
}
dependencies {
...
classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:$navigation_version"
}
In app/build.gradle
apply plugin: 'androidx.navigation.safeargs'
...
In Navigation graph
<fragment
android:id="#+id/source_fragment_id"
android:name="app.test.SourceFragment"
android:label="#string/source_fragment_label"
tools:layout="#layout/source_fragment_layout">
<action
android:id="#+id/action_source_fragment_to_destination_fragment"
app:destination="#id/destination_fragment_id"
...
/>
</fragment>
<fragment
android:id="#+id/destination_fragment_id"
android:name="app.test.DestinationFragment"
android:label="#string/destination_fragment_label"
tools:layout="#layout/destination_fragment_layout">
<argument
android:name="variableName"
app:argType="app.test.data.model.CustomModel" />
...
</fragment>
Note: CustomModel should be Parcelable or Serializable.
When navigating to this DestinationFragment from SourceFragment
val direction = SourceFragmentDirections.ActionSourceFragmentToDestinationFragment(customModel)
findNavController().navigate(direction)
Now retrieving the value from bundle in DestinationFragment
...
import app.test.DestinationFragmentArgs.fromBundle
class DestinationFragment : Fragment() {
val variableName by lazy {
fromBundle(arguments!!).variableName
}
...
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
Log.e(DESTINATION_FRAGMENT_TAG,"onCreateView")
//Can use CustomModel variable here i.e. variableName
}
}
Right now you can't use safe args with types apart from integer, string, inferred and reference, there's an issue opened asking for other types.
What you can do now is to normally pass a bundle when using the navigate() method to navigate to a destination:
var bundle = bundleOf("amount" to amount)
view.findNavController().navigate(R.id.confirmationAction, bundle)
And you can use the usual getArguments (or just arguments in kotlin) to retrieve that:
val tv = view.findViewById(R.id.textViewAmount)
tv.text = arguments.getString("amount")
You can use boolean, reference, integer, long, string, enum, parcelable and even serializable. But forget about the last one ;-)
Better use the latest plugin version safe-args-gradle-plugin:1.0.0-alpha08 and specify the fully qualified classname:
<fragment
...>
<argument
android:name="data"
app:argType="com.example.ParcelableData" />
</fragment>
from your
package com.example
data class ParcelableData(val content: String) : Parcelable { ... }
And you can send arrays of all the argTypes:
<argument
android:name="data"
app:argType="string[]" />

Categories

Resources