Navigation from my BottomNavigationView isn't working correctly and I'm stumped.
When I first open the app, it works fine. I click a button in the BottomNavigationView and the corresponding fragment is displayed.
I start seeing the bug after I navigate from fragment A to fragment B using a "go to B" button on fragment A. From then on, when I click the A button in the BottomNavigationView, B is displayed.
Things I've tried:
I overrode all the fragment lifecycle callbacks in A (onCreate(), onCreateView(), onViewCreated(), onViewStateRestored(), onStart(), onResume()). I put breakpoints in each. None are hit when in the buggy state.
I created a new app with the Android template for Bottom Navigation Activity, and reproduced the bug there. The only thing I changed was to add a button on fragment A that navigates to fragment B.
I'm new to both Android and stack overflow so let me know if I'm doing anything silly. Thanks in advance for any help!
Here's the code that reproduces the bug. (Fragment A is the HomeFragment)
HomeFragment.kt
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.findNavController
import androidx.navigation.fragment.findNavController
import com.example.myapplication.R
import com.example.myapplication.databinding.FragmentHomeBinding
class HomeFragment : Fragment() {
private var _binding: FragmentHomeBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val homeViewModel =
ViewModelProvider(this).get(HomeViewModel::class.java)
_binding = FragmentHomeBinding.inflate(inflater, container, false)
val root: View = binding.root
val textView: TextView = binding.textHome
homeViewModel.text.observe(viewLifecycleOwner) {
textView.text = it
}
return root
}
// This is the code I added
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.button.setOnClickListener {view ->
view.findNavController().navigate(HomeFragmentDirections.actionNavigationHomeToNavigationDashboard())
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
fragment_home.xml
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.home.HomeFragment">
<TextView
android:id="#+id/text_home"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:textAlignment="center"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!-- I added this button -->
<Button
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Dashboard"
app:layout_constraintTop_toBottomOf="#id/text_home"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
mobile_navigation.xml
<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/navigation_home">
<fragment
android:id="#+id/navigation_home"
android:name="com.example.myapplication.ui.home.HomeFragment"
android:label="#string/title_home"
tools:layout="#layout/fragment_home" >
<!-- I added this action -->
<action
android:id="#+id/action_navigation_home_to_navigation_dashboard"
app:destination="#id/navigation_dashboard" />
</fragment>
<fragment
android:id="#+id/navigation_dashboard"
android:name="com.example.myapplication.ui.dashboard.DashboardFragment"
android:label="#string/title_dashboard"
tools:layout="#layout/fragment_dashboard" />
<fragment
android:id="#+id/navigation_notifications"
android:name="com.example.myapplication.ui.notifications.NotificationsFragment"
android:label="#string/title_notifications"
tools:layout="#layout/fragment_notifications" />
</navigation>
MainActivity.kt (didn't modify this)
package com.example.myapplication
import android.os.Bundle
import com.google.android.material.bottomnavigation.BottomNavigationView
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupActionBarWithNavController
import androidx.navigation.ui.setupWithNavController
import com.example.myapplication.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val navView: BottomNavigationView = binding.navView
val navController = findNavController(R.id.nav_host_fragment_activity_main)
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
val appBarConfiguration = AppBarConfiguration(
setOf(
R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications
)
)
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)
}
}
activity_main.xml (didn't modify this)
<?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:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="?attr/actionBarSize">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/nav_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="0dp"
android:layout_marginStart="0dp"
android:background="?android:attr/windowBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="#menu/bottom_nav_menu" />
<fragment
android:id="#+id/nav_host_fragment_activity_main"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="#id/nav_view"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="#navigation/mobile_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
Top level build.gradle
// Top-level build file where you can add configuration options common to all sub-projects/modules.
// I added everything within the buildscript block
buildscript {
repositories {
google()
}
dependencies {
classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.4.1")
}
}
plugins {
id 'com.android.application' version '7.1.2' apply false
id 'com.android.library' version '7.1.2' apply false
id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
}
task clean(type: Delete) {
delete rootProject.buildDir
}
build.gradle (app)
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
// I added this line
id 'androidx.navigation.safeargs.kotlin'
}
android {
compileSdk 31
defaultConfig {
applicationId "com.example.myapplication"
minSdk 26
targetSdk 31
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
viewBinding true
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1'
implementation 'androidx.navigation:navigation-fragment-ktx:2.4.1'
implementation 'androidx.navigation:navigation-ui-ktx:2.4.1'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
I figured it out. The fix is to add the last three lines to the action tag in mobile_navigation.xml:
<action
android:id="#+id/action_navigation_home_to_navigation_dashboard"
app:destination="#id/navigation_dashboard"
app:restoreState="true"
app:popUpTo="#id/navigation_home"
app:popUpToSaveState="true"
/>
More information about why this works: https://developer.android.com/guide/navigation/multi-back-stacks#nav-xml
I have searched for a lot of solutions to this problem but none of them worked for me.
Finally, I have found a lazy hack that can "fix" this "bug". I have already tested this solution for a while and no issues have been found so far.
Note: this is NOT an official way to fix this issue. I suggest NOT to use this solution UNTIL none of the solutions worked for you.
Solution:
Replace this line:
navController.navigate(R.id.navigation_dashboard);
with this line:
findViewById(R.id.nav_view).findViewById(R.id.navigation_dashboard).performClick();
R.id.nav_view is the id of the BottomNavigationView component. It could be inside your activity_main.xml.
R.id.navigation_dashboard is the id of the fragment that you want to navigate to. It could be inside your res/navigation/mobile_navigation.xml or res/navigation/nav_graph.xml.
Related
It seems I have some problem with kapt. I study Android and try to create app with Room and Coroutines.
I get this kind of mistakes:
enter image description here
and my onclicklistener at XML is underlined with red, and the mistake here is "Cannot find identifier 'onSave' ".
I also tried to set onclick listener at the fragment, but I got the same mistakes.
Neither invalidating caches nor rebuilding project helps.
AddViewModel.kt:
`package com.example.memorizewords.add
import android.app.Application
import androidx.lifecycle.*
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
import androidx.lifecycle.viewmodel.CreationExtras
import com.example.memorizewords.database.Word
import com.example.memorizewords.database.WordDatabase
import com.example.memorizewords.database.WordDatabaseDao
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
class AddViewModel(
val database: WordDatabaseDao?,
application: Application) : AndroidViewModel(application) {
private val _targetWord = MutableLiveData<String>()
private val _nativeWord = MutableLiveData<String>()
private suspend fun insert(word: Word) {
database?.insert(word)
}
private fun onSave() {
runBlocking {
viewModelScope.launch(Dispatchers.IO) {}
// val newWord = Word(0L, _targetWord.value!!, _nativeWord.value!!, true)
val newWord = Word(0L, "String", "String", true)
insert(newWord)
}
}
companion object {
val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
#Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(
modelClass: Class<T>,
extras: CreationExtras
): T {
val application = checkNotNull(extras[APPLICATION_KEY])
val dataSource = WordDatabase.getInstance(application).WordDatabaseDao
AddViewModel(dataSource, application)
return AddViewModel(dataSource, application) as T
}
}
}
}`
AddFragment.kt:
`package com.example.memorizewords.add
import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import android.view.View
import android.widget.Toast
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import com.example.memorizewords.R
import com.example.memorizewords.databinding.FragmentAddBinding
class AddFragment : Fragment() {
private val viewModel: AddViewModel by viewModels { AddViewModel.Factory }
lateinit var targetWord: String
lateinit var nativeWord: String
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val binding: FragmentAddBinding = DataBindingUtil.inflate(
inflater, R.layout.fragment_add, container, false)
binding.setLifecycleOwner(this)
binding.addViewModel = viewModel
}`
XML:
`<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="addViewModel"
type="com.example.memorizewords.add.AddViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="#+id/targetBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="#dimen/button_margin_left_right"
android:layout_marginEnd="#dimen/button_margin_left_right"
android:hint="#string/enterTarget"
android:textSize="#dimen/edit_text_size"
app:layout_constraintBottom_toTopOf="#+id/guideline2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.Guideline
android:id="#+id/guideline2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.15" />
<EditText
android:id="#+id/nativeBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="#dimen/button_margin_left_right"
android:layout_marginEnd="#dimen/button_margin_left_right"
android:hint="#string/enterNative"
android:textSize="#dimen/edit_text_size"
app:layout_constraintBottom_toTopOf="#+id/guideline3"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/guideline2" />
<androidx.constraintlayout.widget.Guideline
android:id="#+id/guideline3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.3" />
<Button
android:id="#+id/saveButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="#dimen/save_button_margin"
android:layout_marginEnd="#dimen/save_button_margin"
android:text="#string/save"
android:textSize="#dimen/button_text_size"
android:onClick="#{() -> addViewModel.onSave()}" //onSave here is underlined with red
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/guideline3"
app:layout_constraintVertical_bias="0.1" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>`
module's build.gradle
`plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
id 'androidx.navigation.safeargs.kotlin'
}
android {
compileSdk 32
defaultConfig {
applicationId "com.example.memorizewords"
minSdk 21
targetSdk 32
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
dataBinding true
viewBinding true
}
}
dependencies {
// Android KTX
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'com.google.android.material:material:1.7.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
// ViewModel and LiveData
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
// Navigation
implementation 'androidx.navigation:navigation-fragment-ktx:2.5.3'
implementation 'androidx.navigation:navigation-ui-ktx:2.5.3'
// Room and Lifecycle dependencies
implementation "androidx.room:room-runtime:2.4.3"
kapt "androidx.room:room-compiler:2.4.3"
// Kotlin Extensions and Coroutines support for Room
implementation("androidx.room:room-ktx:2.4.3")
// Coroutines
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1"
// Testing
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.4'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
implementation 'com.jakewharton.timber:timber:4.7.1'
}`
app's build.gradle
`plugins {
id 'com.android.application' version '7.2.0' apply false
id 'com.android.library' version '7.2.0' apply false
id 'org.jetbrains.kotlin.android' version '1.7.10' apply false
id 'androidx.navigation.safeargs.kotlin' version '2.4.1' apply false
}
task clean(type: Delete) {
delete rootProject.buildDir
}`
First of all, I'm new into kotlin and I found issues within my learning process. So I found these errors:
"Cannot find identifier 'Current Item"
The errors occured in the xml file. here is code of the xml file:
<layout 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">
<data>
<variable
name="args"
type="com.example.todoapp.fragments.update.UpdateFragment" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="24dp"
tools:context=".fragments.update.UpdateFragment">
<EditText
android:id="#+id/current_title_et"
android:layout_width="0dp"
android:layout_height="60dp"
android:background="#drawable/custom_input"
android:ems="10"
android:hint="#string/title"
android:inputType="textPersonName"
android:paddingStart="24dp"
android:paddingEnd="24dp"
android:text="#{args.currentItem.title}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Spinner
android:id="#+id/current_priorities_spinner"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_marginTop="8dp"
android:background="#drawable/custom_input"
android:entries="#array/priorities"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:parsePriorityToInt="#{args.currentItem.priority}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/current_title_et" />
<EditText
android:id="#+id/current_description_et"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="8dp"
android:background="#drawable/custom_input"
android:ems="10"
android:gravity="top|start"
android:hint="#string/description"
android:inputType="textMultiLine"
android:paddingStart="24dp"
android:paddingTop="16dp"
android:paddingEnd="24dp"
android:text="#{args.currentItem.description}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/current_priorities_spinner" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
and this is the kt file
package com.example.todoapp.fragments.update
import android.app.AlertDialog
import android.os.Bundle
import android.view.*
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import com.example.todoapp.R
import com.example.todoapp.data.models.ToDoData
import com.example.todoapp.data.viewmodel.ToDoViewModel
import com.example.todoapp.databinding.FragmentUpdateBinding
import com.example.todoapp.fragments.SharedViewModel
class UpdateFragment : Fragment() {
private val args by navArgs<UpdateFragmentArgs>()
private val mSharedViewModel: SharedViewModel by viewModels()
private val mToDoViewModel: ToDoViewModel by viewModels()
private var _binding: FragmentUpdateBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Data binding
_binding = FragmentUpdateBinding.inflate(inflater, container, false)
// Set Menu
setHasOptionsMenu(true)
// Spinner Item Selected Listener
binding.currentPrioritiesSpinner.onItemSelectedListener = mSharedViewModel.listener
return binding.root
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.update_fragment_menu, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_save -> updateItem()
R.id.menu_delete -> confirmItemRemoval()
}
return super.onOptionsItemSelected(item)
}
private fun updateItem() {
val title = binding.currentTitleEt.text.toString()
val description = binding.currentDescriptionEt.text.toString()
val getPriority = binding.currentPrioritiesSpinner.selectedItem.toString()
val validation = mSharedViewModel.verifyDataFromUser(title, description)
if (validation) {
// Update Current Item
val updatedItem = ToDoData(
args.currentItem.id,
title,
mSharedViewModel.parsePriority(getPriority),
description
)
mToDoViewModel.updateData(updatedItem)
Toast.makeText(requireContext(), "Successfully updated!", Toast.LENGTH_SHORT).show()
// Navigate back
findNavController().navigate(R.id.action_updateFragment_to_listFragment)
} else {
Toast.makeText(requireContext(), "Please fill out all fields.", Toast.LENGTH_SHORT)
.show()
}
}
// Show AlertDialog to Confirm Item Removal
private fun confirmItemRemoval() {
val builder = AlertDialog.Builder(requireContext())
builder.setPositiveButton("Yes") { _, _ ->
mToDoViewModel.deleteItem(args.currentItem)
Toast.makeText(
requireContext(),
"Successfully Removed: ${args.currentItem.title}",
Toast.LENGTH_SHORT
).show()
findNavController().navigate(R.id.action_updateFragment_to_listFragment)
}
builder.setNegativeButton("No") { _, _ -> }
builder.setTitle("Delete '${args.currentItem.title}'?")
builder.setMessage("Are you sure you want to remove '${args.currentItem.title}'?")
builder.create().show()
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
this is the plugin and dependency
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: "kotlin-kapt"
apply plugin: "androidx.navigation.safeargs.kotlin"
apply plugin: "kotlin-parcelize"
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.example.todoapp"
minSdkVersion 26
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
buildFeatures{
dataBinding = true
viewBinding = true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation 'androidx.core:core-ktx:1.5.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
// Navigation Component
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
// Room components
implementation "androidx.room:room-runtime:2.3.0"
kapt "androidx.room:room-compiler:2.3.0"
implementation "androidx.room:room-ktx:2.3.0"
androidTestImplementation "androidx.room:room-testing:2.3.0"
// Lifecycle components
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
implementation "androidx.lifecycle:lifecycle-common-java8:2.3.1"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
// Kotlin components
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0-native-mt"
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0-native-mt"
}
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.2.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.10"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
Any help or explanation would be appreciated
This question already has an answer here:
How to tie fragment destination to menu items in bottom nav bar?
(1 answer)
Closed 1 year ago.
I Use Navigation Components.
I Have A Bottom Navigation And I Want To Switch Between Fragments. But When I Use setupWithNavController Method , Nothing Happens.
Even The Navigation Options Doesn't Change.
setOnNavigationItemSelectedListener Method Works Correctly But I Want To Know What Is My Problem.
Here Is activity_main.xml
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/nav_home"
/>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottom_navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="?android:attr/windowBackground"
app:menu="#menu/nav_menu"
/>
MainActivity class :
import android.os.Bundle
import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupWithNavController
import com.google.android.material.bottomnavigation.BottomNavigationView
class MainActivity : AppCompatActivity() {
private lateinit var navController: NavController
private lateinit var appBarConfiguration: AppBarConfiguration
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = navHostFragment.navController
appBarConfiguration =
AppBarConfiguration(setOf(R.id.bottomNavFragment, R.id.infoFragment))
val bottomNav = findViewById<BottomNavigationView>(R.id.bottom_navigation)
bottomNav.setupWithNavController( navController);
}
}
navigation :
<?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/nav_home"
app:startDestination="#id/bottomNavFragment">
<fragment
android:id="#+id/bottomNavFragment"
android:name="com.example.bottomnavtestproject.BlankFragment1"
android:label="Home"
tools:layout="#layout/fragment_blank1" />
<fragment
android:id="#+id/infoFragment"
android:name="com.example.bottomnavtestproject.BlankFragment2"
android:label="Info"
tools:layout="#layout/fragment_blank2" />
</navigation>
menu :
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="#+id/nav_favorite"
android:icon="#drawable/ic_baseline_favorite_24"
android:title="Favorite"/>
<item
android:id="#+id/nav_home1"
android:icon="#drawable/ic_baseline_home_24"
android:title="Home"/>
build.gradle :
plugins {
id 'com.android.application'
id 'kotlin-android'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.1"
defaultConfig {
applicationId "com.example.bottomnavtest"
minSdkVersion 19
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
def fragment_version = "1.3.1"
// Kotlin
implementation "androidx.fragment:fragment-ktx:$fragment_version"
def nav_version = "2.3.3"
// Kotlin
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
// Feature module Support
implementation "androidx.navigation:navigation-dynamic-features-fragment:$nav_version"
// Testing Navigation
androidTestImplementation "androidx.navigation:navigation-testing:$nav_version"
}
set menu items id's same as in navigation fragments id
For example if in your navigation fragment id android:id="#+id/bottomNavFragment"
In menu xml file item id should be the same, like:
<item android:id="#+id/bottomNavFragment"...
I'm currently developing an application using android-studio with Kotlin.
This app has
a common value I can use through fragments
a fragment class for main content
a fragment class for bottom sheet
I want to show a new value on the main fragment view right after I change the common value at edittext in bottom sheet.
But in the case of my codes, the common value doesn't change when I' back to the main fragment view from the bottom sheet...
How can I solve this situation?
Here are the codes:
build.gradle
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.sample.bottomsheetvalue"
minSdkVersion 16
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.0.0-beta01'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.legacy:legacy-support-v4:1.0.0-beta01'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.0-alpha4'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0-alpha4'
implementation "androidx.fragment:fragment-ktx:1.3.0-alpha04"
implementation "com.google.android.material:material:1.1.0-alpha06"
}
AppState.kt
package com.sample.bottomsheetvalue
import androidx.lifecycle.ViewModel
class AppState: ViewModel() {
var value:String = "initialValue"
}
MainActivity.kt
package com.sample.bottomsheetvalue
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<fragment
android:id="#+id/main_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.tomato_love.bottomsheetvalue.MainFragment"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
MainFragment.kt
package com.sample.bottomsheetvalue
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
class MainFragment : Fragment() {
lateinit var appState: AppState
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
activity?.run {
appState = ViewModelProvider(this).get(AppState::class.java)
}
val view = inflater.inflate(R.layout.fragment_main, container, false)
val button = view.findViewById<Button>(R.id.button)
val textView = view.findViewById<TextView>(R.id.textView)
textView.text = appState.value
button.setOnClickListener {
val fm = activity!!.supportFragmentManager
val bottomSheet =BottomSheet()
bottomSheet.show(fm, "navigation_bottom_sheet")
}
return view
}
}
fragment_main.xml
<?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" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainFragment"
android:id="#+id/constraintLayoutFragment">
<Button
android:id="#+id/button"
android:text="show bottom sheet"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
/>
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="32sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="32dp"
app:layout_constraintTop_toBottomOf="#+id/button"/>
</androidx.constraintlayout.widget.ConstraintLayout>
BottomSheet.kt
package com.sample.bottomsheetvalue
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.widget.EditText
import androidx.lifecycle.ViewModelProvider
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
class BottomSheet : BottomSheetDialogFragment() {
lateinit var appState: AppState
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
activity?.run {
appState = ViewModelProvider(this).get(AppState::class.java)
}
val view = inflater.inflate(R.layout.fragment_bottom_sheet, container, false)
val editText = view.findViewById<EditText>(R.id.editText)
editText.setOnEditorActionListener { v, actionId, event ->
when (actionId) {
EditorInfo.IME_ACTION_DONE -> {
appState.value = editText.text.toString()
println(appState.value)
true
}
else -> false
}
}
return view
}
}
fragment_bottom_sheet.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".BottomSheet">
<EditText
android:id="#+id/editText"
android:layout_width="match_parent"
android:layout_height="500dp"
android:inputType="textPersonName"
android:ems="10"
android:imeOptions="actionDone"
/>
</FrameLayout>
Android Studio : 3.3.2
Add a Livedata to your view model class:
val data: MutableLiveData<String> = MutableLiveData("Initial Value")
And observe that in your fragment:
appState.data.observe(viewLifecycleOwner, Observer {
// Update your ui here
})
And also in your bottom sheet update the data:
data.postValue("newValue")
I'm currently developing an application using android-studio with Kotlin.
I want to use a bottom sheet, but when I call the show() method I have an error and I have no idea how to solve it...
When I hover a cursor on the show() I have this error message.
None of the following functions can be called with the arguments supplied.
・show(FragmentManager!, String!) defined in com sample.myprojectname.BottomSheetFragment
・show(FragmentTransaction!, String!) defined in com sample.myprojectname.BottomSheetFragment
How can I solve this error?
Here are the codes:
build.gradle
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.sample.bottomsheetvalue"
minSdkVersion 16
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:2.0.1'
implementation 'com.android.support:support-v4:28.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'com.google.android.material:material:1.0.0'
}
MainActivity.kt
package com.sample.bottomsheetvalue
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val button = findViewById<Button>(R.id.button)
button.setOnClickListener {
val bottomSheet = BottomSheet()
bottomSheet.show(supportFragmentManager,"navigation_bottom_sheet")
//↑ I have the error here
}
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:text="Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/button"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
/>
</android.support.constraint.ConstraintLayout>
BottomSheet.kt
package com.sample.bottomsheetvalue
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
class BottomSheet : BottomSheetDialogFragment()
{
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_bottom_sheet, container, false)
return view
}
}
fragment_bottom_sheet.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".BottomSheet">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="#string/hello_blank_fragment"/>
</FrameLayout>
Android Studio : 3.3.2