I've read a few lightly documented explanations of this on StackOverflow, but nothing that helps. What I've done is created a sample app with Navigation Drawer Activity using Android Studio that features the hamburger nav icon and a sliding nav drawer functionality. It works great!
However, when I copy every aspect of that sample app into my app, from the MainActivity, AppManifest, Navigation Map, to the theme settings to the layout XMLs, I still get a bloody back arrow and fragment title.
What code actually controls this?
This is what I need:
This is what I'm getting:
[]
My fear is that using DataBinding might be breaking some associations for the AppNavBar settings.
This is my main_activity.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"
xmlns:tools="http://schemas.android.com/tools">
<androidx.drawerlayout.widget.DrawerLayout
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:openDrawer="start">
<include
android:id="#+id/app_bar_main"
layout="#layout/app_bar_main"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.google.android.material.navigation.NavigationView
android:id="#+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="#layout/nav_header_main"
app:menu="#menu/sc_drawer_menu_items" />
</androidx.drawerlayout.widget.DrawerLayout>
</layout>
This is my MainActivity class.
open class MainActivity : AppCompatActivity() {
private lateinit var appBarConfiguration: AppBarConfiguration
private lateinit var binding: MainActivityBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.main_activity)
setSupportActionBar(binding.appBarMain.toolbar)
val drawerLayout: DrawerLayout = binding.drawerLayout
val navView: NavigationView = binding.navView
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment_content_main) as NavHostFragment
val navController = navHostFragment.navController
appBarConfiguration = AppBarConfiguration(
setOf(
R.id.sc_menu_i_inv_count,
R.id.sc_menu_i_work_orders,
R.id.sc_menu_i_ins_queue,
R.id.sc_menu_i_return_queue,
R.id.sc_menu_i_settings,
R.id.sc_menu_i_sign_out,
R.id.sc_menu_image_list_view
), drawerLayout
)
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.main_activity, menu)
return true
}
override fun onSupportNavigateUp(): Boolean {
val navController = findNavController(R.id.nav_host_fragment_content_main)
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
}
Other than some changes for DataBindingUtils, it should perform the same as I understand it.
Found a solution. Added this code to my MainActivity.kt
val drawerLayout: DrawerLayout = binding.drawerLayout
val navView: NavigationView = binding.navView
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment_content_main) as NavHostFragment
val navController = navHostFragment.navController
getSupportActionBar()?.setDisplayHomeAsUpEnabled(true)
getSupportActionBar()?.setHomeButtonEnabled(true)
appBarConfiguration = AppBarConfiguration(
setOf(
R.id.sc_menu_i_inv_count,
R.id.sc_menu_i_work_orders,
R.id.sc_menu_i_ins_queue,
R.id.sc_menu_i_return_queue,
R.id.sc_menu_i_settings,
R.id.sc_menu_i_sign_out,
R.id.sc_menu_image_list_view
), drawerLayout
)
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)
actionBarDrawerToggle =
ActionBarDrawerToggle(this,
drawerLayout,
binding.appBarMain.toolbar,
R.string.nav_open,
R.string.nav_close)
drawerLayout.addDrawerListener(actionBarDrawerToggle)
actionBarDrawerToggle.syncState()
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
Related
I want to implement my app. My app went smoothly, but I also got an error with 2022-05-31 14:14:32.663 8626-8626/id.kotlin.belajar E/RecyclerView: No adapter attached; skipping layout; skipping layout.
E/RecyclerView: No adapter attached; skipping layout; skipping layout
Home Activity:
class HomeActivity : DaggerAppCompatActivity(), HomeView {
#Inject
lateinit var presenter: HomePresenter
private lateinit var progressBar: ProgressBar
private lateinit var recyclerView: RecyclerView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_home)
progressBar = findViewById(R.id.pb_home)
recyclerView = findViewById(R.id.rv_home)
presenter.discoverMovie()
}
override fun onShowLoading() {
progressBar.visibility = View.VISIBLE
}
override fun onHideLoading() {
progressBar.visibility = View.GONE
recyclerView.visibility = View.VISIBLE
}
override fun onResponse(results: List<Result<String>>) {
recyclerView.addItemDecoration(DividerItemDecoration(this#HomeActivity,
DividerItemDecoration.VERTICAL))
recyclerView.adapter = HomeAdapter(results)
}
override fun onFailure(t:Throwable) {
Log.e(HomeActivity::class.java.simpleName, "${t.printStackTrace()}")
}
}
Home Adapter:
class HomeAdapter (private val results: List<Result>):
RecyclerView.Adapter<HomeAdapter.HomeViewHolder>(){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeViewHolder {
return HomeViewHolder(
LayoutInflater
.from(parent.context).inflate(
R.layout.item_home,
parent,
false
)
)
}
override fun onBindViewHolder(holder: HomeViewHolder, position: Int){
holder.bind()
}
override fun getItemCount(): Int{
return results.count()
}
inner class HomeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
fun bind() {
with(itemView) {
val title = findViewById<TextView>(R.id.original_title)
title.text
val overview = findViewById<TextView>(R.id.overview)
overview.text
}
}
}
}
Home Presenter:
class HomePresenter(private val view: HomeView, datasource: HomeDatasource) {
fun discoverMovie(){
view.onShowLoading()
val dataSource = Networkmodule.providesHttpAdapter(client =
OkHttpClient()).create(HomeDatasource::class.java)
dataSource.discoverMovie().enqueue(object : Callback<HomeResponse> {
override fun onResponse(call: Call<HomeResponse>, response: Response<HomeResponse>){
view.onHideLoading()
view.onResponse(((response.body()?.results ?: emptyList()) as List<Result<String>>))
}
override fun onFailure(call: Call<HomeResponse>, t:Throwable){
view.onHideLoading()
view.onFailure(t)
}
})
}
}
I need your help.
This is my .xml files:
activity_home:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".presentation.HomeActivity">
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/white"
android:elevation="4dp"
app:layout_constraintTop_toTopOf="parent"
app:title="Belajar"
app:titleTextColor="#color/black"/>
<ProgressBar
android:id="#+id/pb_home"
style="#style/Widget.AppCompat.ProgressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true"
android:indeterminateTint="#color/black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="gone" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rv_home"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="?android:attr/actionBarSize"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:listitem="#layout/item_home" />
</androidx.constraintlayout.widget.ConstraintLayout>
I can see two major problems here :
Adapter instantiation
You do not need to re-instantiate the adapter each time you receive data.
Just instanciate it in the onCreate method with an empty list and update the list each time you have new data (don't forget to call notify method).
And attach the adapter to the recyclerView in the onCreate method as well
Exemple
In your activity you need to :
Declare and instantiate your Adapter as a class attribute.
In the onCreate method attach it to the recyclerView
On the onResponse method call a new method of your adapter called " update "
class HomeActivity : DaggerAppCompatActivity(), HomeView {
#Inject
lateinit var presenter: HomePresenter
private lateinit var progressBar: ProgressBar
private lateinit var recyclerView: RecyclerView
private val homeAdapter = HomeAdapter(emptyList())
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_home)
progressBar = findViewById(R.id.pb_home)
recyclerView = findViewById(R.id.rv_home)
recyclerView.adapter = homeAdapter
presenter.discoverMovie()
}
override fun onShowLoading() {
progressBar.visibility = View.VISIBLE
}
override fun onHideLoading() {
progressBar.visibility = View.GONE
recyclerView.visibility = View.VISIBLE
}
override fun onResponse(results: List<Result<String>>) {
recyclerView.addItemDecoration(DividerItemDecoration(this#HomeActivity,
DividerItemDecoration.VERTICAL))
homeAdapter.update(results)
}
override fun onFailure(t:Throwable) {
Log.e(HomeActivity::class.java.simpleName, "${t.printStackTrace()}")
}
}
In your adapter :
Make results a mutableList
Create the update method
Clear the results list
Add your newResults list to your list
notifyDataSetChanged to redraw the list with new values
class HomeAdapter (private val results: MutableList<Result>):
RecyclerView.Adapter<HomeAdapter.HomeViewHolder>(){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeViewHolder {
return HomeViewHolder(
LayoutInflater
.from(parent.context).inflate(
R.layout.item_home,
parent,
false
)
)
}
override fun onBindViewHolder(holder: HomeViewHolder, position: Int){
holder.bind()
}
override fun getItemCount(): Int{
return results.count()
}
fun update(newResults: List<Result>) {
results.apply {
clear()
addAll(newResults)
}
notifyDataSetChanged()
}
inner class HomeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
fun bind() {
with(itemView) {
val title = findViewById<TextView>(R.id.original_title)
title.text
val overview = findViewById<TextView>(R.id.overview)
overview.text
}
}
}
}
Layout manager missing ?
We don't have your xml code so I don't know if you added the layoutManager on the xml like this (assuming you want a linearManager) :
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
You can do it programaticaly using the layoutManager attribute on the recyclerView :
recyclerView.layoutManager = LinearLayoutManager()
You can set it in the onCreate method (if it is an activity) or onViewCreated (if it's a fragment) before attaching your adapter to the recyclerView
To avoid errors like that, the best way is to setup your recyclerView as soon as possible in the activity (onCreate) or fragment (onViewCreated) lifecycle.
With the help of room, i am testing a simple app to save words. I have 2 layout fragment- home(main) and myword(second). I am trying to put the following code in myword fragment to initialize recyclerview, i am getting unresolve error. What am i putting wrong?
frag_mywords.xml
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/words_dail"
android:layout_width="0dp"
android:layout_height="0dp"
tools:listitem="#layout/recykel_list"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
TakerFragment.kt
class TakerFragment : Fragment() {
private lateinit var takerViewModel: TakerViewModel
private var _binding: FragMywordsBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
takerViewModel =
ViewModelProvider(this).get(TakerViewModel::class.java)
_binding = FragMywordsBinding.inflate(inflater, container, false)
val root: View = binding.root
val recyclerView = findViewById<RecyclerView>(R.id.words_dail)
val adapter = WordListAdapter(this)
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(this)
return root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
How do i correctly solved this line?
val recyclerView = findViewById<RecyclerView>(R.id.words_dail)
val adapter = WordListAdapter(this)
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(this)
There are multiple issues here:
First change val recyclerView = findViewById<RecyclerView>(R.id.words_dail) with val recyclerView = words_dail.
Then, the context of your WorldListAdapter is wrong, it should be val adapter = WordListAdapter(requireContext()) and not this.
Third, you can set the layoutManager in the xml with this:
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/words_dail"
android:layout_width="0dp"
android:layout_height="0dp"
tools:listitem="#layout/recykel_list"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" <--
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
With Databinding you could also set the adapter in the xml but I don't know if you use databinding in your adapter.
I read some document about using recyclingview for activity. Now i try to use recycleview to my fragment. the problem is my fragment look empty when i execute.
fragment:
class KategoriFragment : Fragment() {
var araclarKategori = ArrayList<AracMarka>()
private lateinit var galleryViewModel: GalleryViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
veriKaynaginiDoldur()
galleryViewModel =
ViewModelProviders.of(this).get(GalleryViewModel::class.java)
var root = inflater.inflate(R.layout.fragment_category, container, false)
veriKaynaginiDoldur()
var rvarackategori = root.findViewById(R.id.rvarackategori) as RecyclerView
var MyAdapter = AracMarkaAdapter(araclarKategori)
rvarackategori.adapter = MyAdapter
/
return root
}
fun veriKaynaginiDoldur(): ArrayList<AracMarka> {
var aracLogo = arrayOf(R.drawable.opellogo, R.drawable.chevroletlogo)
var aracismi = resources.getStringArray(R.array.arabaisim)
for (i in 0 until min(aracismi.size, aracLogo.size)) {
var eklenecekaracKategori = AracMarka(aracismi[i], aracLogo[i])
araclarKategori.add(eklenecekaracKategori)
}
return araclarKategori
}
}
I create an adapter. I think there is no problem on it.
adapter:
class AracMarkaAdapter(tumKategori: ArrayList<AracMarka>) :
RecyclerView.Adapter<AracMarkaAdapter.AracMarkaViewHolder>() {
var araclar = tumKategori
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AracMarkaViewHolder {
var inflater = LayoutInflater.from(parent.context)
var arackategori = inflater.inflate(R.layout.arac_kategori, parent, false)
return AracMarkaViewHolder(arackategori)
}
override fun getItemCount(): Int {
return araclar.size
}
override fun onBindViewHolder(holder: AracMarkaViewHolder, position: Int) {
holder.aracismi.text=araclar.get(position).aracAdi
holder.aracLogo.setImageResource(araclar.get(position).aracLogo)
}
class AracMarkaViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var teksatirKategori= itemView
var aracismi= teksatirKategori.tvaracAdi
var aracLogo=teksatirKategori.img_arac_sembol
}
}
fragment 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">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rvarackategori"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
So, when i click button, fragment opens but it is empty. Do you have any idea about it?
After modifying the data in your list in veriKaynaginiDoldur(), you need to call myAdapter.notifyDataSetChanged() so it knows to rebind the data. Or you could call veriKaynaginiDoldur() before you instantiate your adapter.
Edit: Your other error is in your for loop within veriKaynaginiDoldur(). You are making a range using the size of the araclarKategori list when it is still zero.
Instead of
for (i in 0..araclarKategori.size - 1)
use
for (i in 0 until min(aracLogo.size, aracismi.size))
you have to call this veriKaynaginiDoldur() function after the below
lines of code below I have mentioned please check
var rvarackategori = root.findViewById(R.id.rvarackategori) as RecyclerView
var MyAdapter = AracMarkaAdapter(araclarKategori)
rvarackategori.adapter = MyAdapter
veriKaynaginiDoldur()
I'm currently writing an app with the new (to me) navigation component. I've got the basics down with a single navigation graph to navigate around my app, I've got a fragment with a BottomNavigationView in which has 3 seperate fragments, I've managed to update this to use the navigation component (as far as my problem) using the menu with ids that match the navigation items. My fragments which all previously used newInstance methods to pass a bundle to the onCreate are obviously now not used but I still need to pass a bundle to my fragments.
I haven't been able to find any examples of this being done, as the fragments are implicitly created.
My code is structured as ClientFragment which is the host fragment for the navigation drawer etc which is;
class ClientFragment : Fragment() {
private val viewModel: ClientViewModel by viewModel()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_client, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.client = arguments?.getParcelable(ARG_CLIENT)!!
toolbar_client.title = viewModel.client.name
toolbar_client.setNavigationOnClickListener { Navigation.findNavController(view).navigateUp() }
}
}
This class previously held on onclick listener to my fragments, with a newInstance method which tool viewModel.client.
My fragments in the nav_graph are all similar. The first fragment;
class ClientDetailsFragment : Fragment() {
private val viewModel: ClientViewModel by viewModel()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_client_details, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// viewModel.client = arguments?.getParcelable(ARG_CLIENT)!!
initClientDetails()
}
private fun initClientDetails() {
// text_client_details_name.text = viewModel.client.name
// text_client_details_account_number.text = viewModel.client.accountNumber
// text_client_details_mobile_number.text = viewModel.client.mobileNumber
// text_client_details_landline_number.text = viewModel.client.landlineNumber
// text_client_details_email.text = viewModel.client.email
// text_client_details_address.text = "NOT YET IMPLEMENTED"
//
// text_client_description_body.text = viewModel.client.description
// text_client_system_details_body.text = viewModel.client.systemDetails
}
}
The app crashes on the commented out line;
// viewModel.client = arguments?.getParcelable(ARG_CLIENT)!!
My navigation graph and menu are;
nav 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/client_nav_graph"
app:startDestination="#id/clientDetailsFragment">
<fragment
android:id="#+id/clientCustomersFragment"
android:name="com.management.engineering.alarm.alarmengineermanagement.features.client.ClientCustomersFragment"
android:label="ClientCustomersFragment"
tools:layout="#layout/fragment_client_customers" />
<fragment
android:id="#+id/clientDetailsFragment"
android:name="com.management.engineering.alarm.alarmengineermanagement.features.client.ClientDetailsFragment"
android:label="ClientDetailsFragment"
tools:layout="#layout/fragment_client_details"/>
<fragment
android:id="#+id/clientJobHistoryFragment"
android:name="com.management.engineering.alarm.alarmengineermanagement.features.client.ClientJobHistoryFragment"
android:label="ClientJobHistoryFragment"
tools:layout="#layout/fragment_client_job_history" />
</navigation>
menu;
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="#+id/clientDetailsFragment"
android:icon="#drawable/ic_launcher_foreground"
android:title="Details"/>
<item
android:id="#+id/clientJobHistoryFragment"
android:icon="#drawable/ic_launcher_foreground"
android:title="Job History"/>
<item
android:id="#+id/clientCustomersFragment"
android:icon="#drawable/ic_launcher_foreground"
android:title="Customers"/>
</menu>
I have found that you can add arguments to the navigation graph, but have found nothing about where to put them for this specific scenario, I'm also aware of being able to manual add bundles when navigating using .navigate.
Is there a way for me to set in my ClientFragment the arguments for each of these fragments to be
viewModel.client
Update:
My argument issue was solved by using a view model that's shared between all of the fragments in the BottomNavigationView (I realised this as I was typing the issue out to my friend) and the navigation itself I added this to the ClientFragment;
bottom_nav_client.setupWithNavController(
Navigation.findNavController(
view.findViewById<View>(R.id.fl_client_nav_container)
)
)
and my xml for fragment_client;
<?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:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar_client"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="#color/colorPrimary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navigationIcon="?attr/NavigationBackIconLight"
app:titleTextColor="#color/white" />
<fragment
android:id="#+id/fl_client_nav_container"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="#id/bottom_nav_client"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/toolbar_client"
app:navGraph="#navigation/client_nav_graph" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottom_nav_client"
android:layout_width="match_parent"
android:layout_height="56dp"
android:background="?android:attr/windowBackground"
app:itemBackground="#color/colorPrimary"
app:itemIconTint="#drawable/bottom_nav_color"
app:itemTextColor="#color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:menu="#menu/client_menu" />
</androidx.constraintlayout.widget.ConstraintLayout>
This is combined with the same navigation graph and menu as shown above.
The Codelabs referred to in the accepted answer don't mention passing arguments to fragments in the BottomNavigationView.
Override the OnNavigationItemSelectedListener set by the setupWithNavController() with a custom one:
val args = Bundle()
bottomNavigationView.setupWithNavController(navController)
bottomNavigationView.setOnNavigationItemSelectedListener { item ->
navController.navigate(item.itemId, args)
true
}
if you are using NavigationComponent :
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem menuItem) {
NavigationUI.onNavDestinationSelected(menuItem, navController);
}
you use this function, inside onNavDestiationSelected function you can see that navigation component passes null to args, so if you wanna pass argument to bottomNavigation fragments, simply you can write navigation function byyourselft and the only change is pass your arguments. as follow:
create a package level function :
fun onNavDestinationSelected(item: MenuItem, navController: NavController, args: Bundle?): Boolean {
val builder = NavOptions.Builder().setLaunchSingleTop(true).setRestoreState(true)
if (
navController.currentDestination!!.parent!!.findNode(item.itemId)
is ActivityNavigator.Destination
) {
builder.setEnterAnim(R.anim.nav_default_enter_anim)
.setExitAnim(R.anim.nav_default_exit_anim)
.setPopEnterAnim(R.anim.nav_default_pop_enter_anim)
.setPopExitAnim(R.anim.nav_default_pop_exit_anim)
} else {
builder.setEnterAnim(R.animator.nav_default_enter_anim)
.setExitAnim(R.animator.nav_default_exit_anim)
.setPopEnterAnim(R.animator.nav_default_pop_enter_anim)
.setPopExitAnim(R.animator.nav_default_pop_exit_anim)
}
if (item.order and Menu.CATEGORY_SECONDARY == 0) {
builder.setPopUpTo(
navController.graph.findStartDestination().id,
inclusive = false,
saveState = true
)
}
val options = builder.build()
return try {
// TODO provide proper API instead of using Exceptions as Control-Flow.
navController.navigate(item.itemId, args, options)
// Return true only if the destination we've navigated to matches the MenuItem
navController.currentDestination?.hierarchy?.any { it.id == item.itemId } == true
} catch (e: IllegalArgumentException) {
false
}
}
and use it inside onNavigationItemSelected function as follow :
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem menuItem) {
OnNavDestinationSelectKt.onNavDestinationSelected(menuItem, navController, yourArgument);
}
The Navigation Architecture Component documentation shows how to define destination arguments, in your concrete case you should create a custom Parcelable class (i.e. Client) and include it as an argument of the corresponding fragment.
<fragment
android:id="#+id/clientCustomersFragment"
android:name="com.management.engineering.alarm.alarmengineermanagement.features.client.ClientCustomersFragment"
android:label="ClientCustomersFragment"
tools:layout="#layout/fragment_client_customers" >
<action
android:id="#+id/client_to_details"
app:destination="#+id/clientDetailsFragment" />
</fragment>
<fragment
android:id="#+id/clientDetailsFragment"
android:name="com.management.engineering.alarm.alarmengineermanagement.features.client.ClientDetailsFragment"
android:label="ClientDetailsFragment"
tools:layout="#layout/fragment_client_details">
<argument
android:name="client"
app:argType="com.management.engineering.alarm.alarmengineermanagement.features.client.Client" />
</fragment>
The 'androidx.navigation.safeargs' gradle plugin will generate the classes ClientToDetails and ClientDetailsFragmentArgs which can be used to pass.retrieve the parameter client.
Source
val client: Client = TODO()
val navController: NavController = TODO()
navController.navigate(ClientToDetails(client))
Destination
val client = ClientDetailsFragmentArgs.fromBundle(arguments).client
This codelabs exactly what you want to do :
https://codelabs.developers.google.com/codelabs/android-navigation/index.html?index=..%2F..index#0
Docs : https://developer.android.com/topic/libraries/architecture/navigation/
I am trying to learn how to use Kotlin/Anko.
I have gone thru the examples here and also cloned the template project and can understand how to do some basic stuff, but as an exercise I wanted to convert this simple activity (generated from a blank activity in Android Studio and converted to Kotlin) to use Anko as well. There are not a lot of examples around for Anko, most are just copies of what is on the above referenced github page.
Can someone demonstrate how to go about and convert the following into Anko DSL?
MainActivity.kt
import android.os.Bundle
import android.support.design.widget.FloatingActionButton
import android.support.design.widget.Snackbar
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import android.view.Menu
import android.view.MenuItem
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val toolBar = findViewById(R.id.toolbar) as Toolbar
setSupportActionBar(toolBar)
val fab = findViewById(R.id.fab) as FloatingActionButton
fab.setOnClickListener { view -> Snackbar.make(view, "Replace this with your own action", Snackbar.LENGTH_LONG).setAction("Action", null).show() }
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_main, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val id = item.itemId
if (id == R.id.action_settings) {
println("settings clicked on ")
return true
}
return super.onOptionsItemSelected(item)
}
}
main_activity.xml
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="#style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<include layout="#layout/content_main" />
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="#dimen/fab_margin"
android:src="#android:drawable/ic_dialog_email" />
</android.support.design.widget.CoordinatorLayout>
content_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
tools:context="com.gmail.npnster.mykotlinfirstproject.MainActivity"
tools:showIn="#layout/activity_main">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:id="#+id/hello"
/>
</RelativeLayout>
menu_main.xml
<menu 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"
tools:context="com.gmail.npnster.mykotlinfirstproject.MainActivity">
<item
android:id="#+id/action_settings"
android:orderInCategory="100"
android:title="#string/action_settings"
app:showAsAction="never" />
</menu>
You can use ankoView method to create Views without DSL methods inside DSL context.
For example, to create a NavigationView one can use
ankoView({ NavigationView(it) }) {
lparams(width = wrapContent, height = matchParent, gravity = Gravity.START)
// more initialization follows
}
This way you can instantiate FloatingActionButton and AppBarLayout, just call their constructors inside ankoView's first argument function. For your convenience, you can make yourself DSL-like functions like in the manual:
fun floatingActionButton(init: FloatingActionButton.() -> Unit) = ankoView({ FloatingActionButton(it) }, init)
Creating a Toolbar is even easier: there is a DSL toolbar method in org.jetbrains.anko.appcompat.v7.
When using Anko DSL, to include another layout, as you did with content_main, one can either use Anko include function or just write a function which will fill in a ViewGroup. You can use this template:
fun ViewGroup.myLayout() {
textView("123")
// more DSL code here
}
Then just call myLayout() inside some ViewGroup initializer.
I know it's a bit late answer, but I hope it helps someone. I did the layout this way (of course some styling is still needed):
class MainUI(val adapter: MainUIAdapter) : AnkoComponent<MainActivity> {
override fun createView(ui: AnkoContext<MainActivity>): View = with(ui) {
coordinatorLayout {
fitsSystemWindows = true
appBarLayout {
toolbar {
setTitleTextColor(Color.WHITE) // so far still needed
id = R.id.toolbar
}.lparams(width = matchParent, height = matchParent)
}.lparams(width = matchParent)
relativeLayout {
id = R.id.container
recyclerView { // just an example
id = R.id.recycler_view
adapter = this#MainUI.adapter
layoutManager = LinearLayoutManager(ctx)
}
}.lparams(width = matchParent, height = matchParent) {
behavior = ScrollingViewBehavior()
}
floatingActionButton {
onClick { doSomething() }
imageResource = R.drawable.ic_add_white_24dp // the plus sign
}.lparams {
gravity = Gravity.BOTTOM or Gravity.END
margin = dip(16)
}
}
}
}
and used in MainActivity like that:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
MainUI(MainUIAdapter(people)).setContentView(this)
toolbar = find<Toolbar>(R.id.toolbar)
setSupportActionBar(toolbar)
}