Jetpack compose navigation architecture without fragment? - android-fragments

I am a bit confused about the new Jetpack compose navigation component androidx.navigation:navigation-compose documented at https://developer.android.com/jetpack/compose/navigation.
Am I right to say that the single-activity architecture with 0 fragment is preferred to the single-activity architecture with multiple fragments when using Jetpack Compose ?
I know we can still use fragments and Jetpack compose in this manner :
class MyFragment: Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply{
setContent {
MyFragmentComposable()
}
}
}
}
But I want to make sure that when using androidx.navigation:navigation-compose, we are not supposed to use fragment anymore, starting like so:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApp()
}
}
}

Yes, you are correct. Using no fragments is preferred. You can use a NavHost to declare your navigation graph.

Related

How to handle back button in case of fragment without removing previous views?

I have one activity (MainActivity) and two fragments(FirstFragment,SecondFragment) as the lifecycle of activity is handled by the os we can press back button and go to previous activity without any hassle . But when I try to go back by pressing back button from one fragment to another fragment although it takes me back to the fragment but removes all my view. Meaning I can't see any element of that fragment . A more detailed explanation is down below with code
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val activity1Button = findViewById<Button>(R.id.activity_1_Button)
activity1Button.setOnClickListener {
supportFragmentManager.beginTransaction()
.replace(R.id.mainActivity, FirstFragment()).addToBackStack(null).commit()
}
}
}
The main activity has a button which can take the user to the next fragment(FirstFragment) while adding it to the BackStack
FirstFragment.kt
class FirstFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
container?.removeAllViews()
val view = inflater.inflate(R.layout.fragment_first, container, false)
val firstFragmentButton = view.findViewById<Button>(R.id.firstFragmentButton)
firstFragmentButton.setOnClickListener {
requireActivity().supportFragmentManager.beginTransaction()
.replace(R.id.firsFragment, SecondFragment())
.addToBackStack("tag")
.commit()
}
return view
}
}
Now from this fragment user can go to another fragment (SecondFragment)
SecondFragment.kt
class SecondFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
container?.removeAllViews()
return inflater.inflate(R.layout.fragment_second, container, false)
}
}
However I am removing all the previous container views as without doing this I the elements of previous fragment/ activity is also visible even when I am in a new fragment. Now when once I have pressed buttons to come to SecondFragment and pressing back button to go to FirstFragment I cant see the buttons anymore in the FirstFragment but through the Layout Inspector it is confirmed that I am in the desired fragments layout. What can be done to solve this issue ?
I have tried to implement this method below but it didn't solve my problem .
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
val manager: FragmentManager = requireActivity().supportFragmentManager
if (manager.backStackEntryCount > 0) {
manager.popBackStack()
}
}
})
}
Thanks in advance .

Type mismatch: inferred type is Model but List<Model> was expectedd

I'm using the room library in a Mvvm architecture application.
There is a main piece and a detail piece.
I go to the detail fragment with id. So I pull the ID from the room. But I will use recyclerview in detail fragment. As you know, there is a list in the adapter class. I am also pulling objects from livedata. These two do not overlap. Do you have a suggestion about it?
This is the detail trailer.
I go to the detail fragment by clicking on Home Fragment. Of course, the relevant information in the list I clicked on will be in the detail section. Think of it like a news site. But I'm going to extract the identity from the elaborate fragmentary room. But I will use recylerview in the detail trailer, but pull it out of the room. The ID and list on the adapter do not match.
Output of the error = Type mismatch: inferred type is Int but List was expected
private var uuidd=0
private lateinit var Biewmodell:Bilimdetayviewmodel
private lateinit var bindingdetay:FragmentBdetayBinding
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
arguments?.let {
uuidd = BdetayFragmentArgs.fromBundle(it).uuidd
}
val viewmodelll:Bilimdetayviewmodel by viewModels()
Biewmodell=viewmodelll
Biewmodell.roomgit(uuidd)
observer()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
bindingdetay=DataBindingUtil.inflate(inflater,R.layout.fragment_bdetay, container, false)
return bindingdetay.root
}
fun observer(){
Biewmodell.getroomlivedata.observe(viewLifecycleOwner, Observer { model ->
model?.let {
val adapterr = dAdapterd(requireContext(),it)
bindingdetay.adapterr=adapterr
}
})
}

Change Button Background in a Fragment with Kotlin

I'm totally new in kotlin so I don't know how many things I'm doing wrong.
I'm trying to set the button background of a fragment from code, but when I start the application in my phone, there is just the background and an empty button
class start : Fragment() {
override fun onCreateView(Layoutinflater: LayoutInflater,
container: ViewGroup?, savedInstantState: Bundle?):View? {
return Layoutinflater.inflate(R.layout.fragment_start, container, false)
val button_start = view!!.findViewById<Button>(R.id.button_start_1_id)
button_start.setBackgroundResource(R.drawable.button_start1)
}
}
And
<ImageButton
android:id="#+id/button_start_1_id"
android:layout_width="124dp"
android:layout_height="69dp"
android:onClick="button_start_pushed">
</ImageButton>
I'm also new in this forum so if a doing something wrong please let me know
Welcome to stackoverflow.
Answer to your question is a simple one. First of all you do not explicitly need to get reference to your view using the following code, and it is redundant.
val button_start = view!!.findViewById<Button>(R.id.button_start_1_id)
You can easily get the reference to your view / imageview using view's id. In your case, and you can just call background property in your view class and set the background using resource.getDrawable(R.drawable.button_start1)
If your API level 16 or above, you can use the following code.
class ImageFragment : Fragment(){
override fun onCreateView(Layoutinflater: LayoutInflater,
container: ViewGroup?, savedInstantState: Bundle?): View? {
return Layoutinflater.inflate(R.layout.fragment_start, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
button_start.background = resources.getDrawable(R.drawable.button_start1)
}
}
resources.getDrawable gives deprecation warning in Kotlin because it requires Resource.Theme as second parameter. If you have Resource.Theme, give it as a second parameter.
And your layout would be the following:
<ImageButton
android:id="#+id/button_start"
android:layout_width="124dp"
android:layout_height="69dp"
android:onClick="button_start_pushed">
</ImageButton>
If you are using API level below 16, then you could use the following code:
class ImageFragment : Fragment(){
override fun onCreateView(Layoutinflater: LayoutInflater,
container: ViewGroup?, savedInstantState: Bundle?): View? {
return Layoutinflater.inflate(R.layout.fragment_start, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
button_start.setImageResource(R.drawable.button_start1)
}
}
Just make sure you have button_start1 icon or img in your drawable, and your layout name is correct.
Another things to note: it would be a lot easier for you to have your own consistent naming convention, or at least follow the recommended one by Kotlin or Java.
I hope it helps you!

lateinit value is always null or not initialized with MapBox

Hello i have an issue with MapBox SDK for android , i use kotlin language in my project and the Viewmap value always came with problem. I'm new in kotlin so maybe i skip something.
i make bottom_navigation_bar so i use fragment to navigate in my application.
my fragment class were i use my variable
package com.example.parky.parky_android
import android.app.Application
import android.content.Context
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity
import android.view.ContextThemeWrapper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.RelativeLayout
import com.mapbox.mapboxsdk.constants.Style
import com.mapbox.mapboxsdk.Mapbox;
import com.mapbox.mapboxsdk.annotations.MarkerOptions;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.OnMapReadyCallback;
class ItemFourFragment : Fragment() {
private lateinit var mapView: MapView
fun onCreate(savedInstanceState: Bundle?, context: Context, inflater: LayoutInflater, container: ViewGroup?) {
super.onCreate(savedInstanceState)
// Mapbox Access token
Mapbox.getInstance(context, getString(R.string.token_mapbox))
val view = inflater.inflate(R.layout.activity_main, container ,false)
mapView = view.findViewById(R.id.mapview)
mapView.onCreate(savedInstanceState)
mapView.getMapAsync({
it.setStyle(Style.SATELLITE)
// Customize map with markers, polylines, etc.
})
}
override fun onStart() {
super.onStart()
mapView.onStart()
}
override fun onResume() {
super.onResume()
mapView.onResume()
}
override fun onPause() {
super.onPause()
mapView.onPause()
}
override fun onStop() {
super.onStop()
mapView.onStop()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
mapView.onSaveInstanceState(outState)
}
override fun onLowMemory() {
super.onLowMemory()
mapView.onLowMemory()
}
override fun onDestroy() {
super.onDestroy()
mapView.onDestroy()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_item_four, container, false)
}
companion object {
fun newInstance(): ItemFourFragment {
val fragment = ItemFourFragment()
return fragment
}
}
}
And my xml files where i display it :
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:mapbox="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ItemFiveFragment">
<com.mapbox.mapboxsdk.maps.MapView
android:id="#+id/mapview"
android:layout_width="match_parent"
android:layout_height="match_parent"
mapbox:mapbox_cameraTargetLat="41.885"
mapbox:mapbox_cameraTargetLng="-87.679"
mapbox:mapbox_styleUrl="#string/mapbox_style_satellite"
mapbox:mapbox_cameraTilt="20"
mapbox:mapbox_cameraZoom="12"/>
</RelativeLayout>
and finally the log
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.parky.parky_android, PID: 30820
kotlin.UninitializedPropertyAccessException: lateinit property mapView has not been initialized
at com.example.parky.parky_android.ItemFourFragment.onStart(ItemFourFragment.kt:42)
at android.support.v4.app.Fragment.performStart(Fragment.java:2372)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1467)
at android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1759)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1827)
at android.support.v4.app.BackStackRecord.executeOps(BackStackRecord.java:797)
at android.support.v4.app.FragmentManagerImpl.executeOps(FragmentManager.java:2596)
at android.support.v4.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2383)
at android.support.v4.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManager.java:2338)
at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:2245)
at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:703)
at android.os.Handler.handleCallback(Handler.java:789)
at android.os.Handler.dispatchMessage(Handler.java:98)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6938)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374)
Do you have any idea what the problem is ?
Thanks for your time.
So here:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_item_four, container, false)
}
You're returning a view that will be attached to the Fragment and actually displayed.
Here:
fun onCreate(savedInstanceState: Bundle?, context: Context, inflater: LayoutInflater, container: ViewGroup?) {
super.onCreate(savedInstanceState)
// Mapbox Access token
Mapbox.getInstance(context, getString(R.string.token_mapbox))
val view = inflater.inflate(R.layout.activity_main, container ,false)
mapView = view.findViewById(R.id.mapview)
mapView.onCreate(savedInstanceState)
mapView.getMapAsync({
it.setStyle(Style.SATELLITE)
// Customize map with markers, polylines, etc.
})
}
You're inflating a new view that is never being attached to anything. It's also inflating a separate layout, activity_main.xml, which I assume does not include a MapView.
Instead, you should resolve your MapView within onViewCreated(), after you've returned your inflated view in onCreateView():
override fun onViewCreated(View view, Bundle savedState) {
mapView = view.findViewById(R.id.mapview).apply {
onCreate(savedState)
getMapAsync { map ->
map.setStyle(Style.SATELLITE)
}
}
}
You also should not use lateinit var for this property, as you're not guaranteed to have a view created (e.g. onCreateView() may never be invoked). One instance of this is if your Fragment is on the backstack and not user visible. On a configuration change or recreation of the Activity, the Fragment on the backstack will go through onCreate(), but will not create a view. In this case, you would run into a crash in onStart() attempting to reference mapView that has not been initialized.
Instead, leave it nullable, and perform the operations with mapView?.doThing().

Error calling setSupportActionBar in a Fragment in Kotlin

I am trying to set up a toolbar in a fragment.
Whilst the Google Developer docs have been updated to include Kotlin code (see this page):
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_my)
// Note that the Toolbar defined in the layout has the id "my_toolbar"
setSupportActionBar(findViewById(R.id.my_toolbar))
it relates to the setup of a toolbar in an activity as opposed to a fragment.
I found this SO post which suggests that you can't just call setSupportActionBar in a fragment. To quote:
Fragments don't have such method setSupportActionBar(). ActionBar is a
property of Activity, so to set your toolbar as the actionBar, your
activity should extend from ActionBarActivity and then you can call in
your Fragment:
...
If you're using AppCompatActivity:
((AppCompatActivity)getActivity()).setSupportActionBar(mToolbar);
However the code given above is in java.
How do I call this in Kotlin?
To access the ActionBar from a Fragment in Kotlin:
if(activity is AppCompatActivity){
(activity as AppCompatActivity).setSupportActionBar(mToolbar)
}
To set an ActionBar title from a Fragment you can do
(activity as AppCompatActivity).supportActionBar?.title = "Title"
or
(activity as AppCompatActivity).supportActionBar?.setTitle(R.string.my_title_string)
There is an implementation in the Navigation codelab from Google that I think will do what I need: enable customisation of the title, menu items and hook into the up navigation for different fragment contexts. Specifically:
The toolbar is included in the main layout xml file (navigation_activity.xml in that codelab) outside of the fragment:
navigation_activity.xml
<LinearLayout>
<android.support.v7.widget.Toolbar/>
<fragment/>
<android.support.design.widget.BottomNavigationView/>
</LinearLayout>
Then setup the toolbar in the main activity file as follows:
MainActivity.kt
class MainActivity : AppCompatActivity() {
private var drawerLayout: DrawerLayout? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.navigation_activity)
val toolbar = findViewById<Toolbar>(R.id.toolbar)
setSupportActionBar(toolbar)
//...
// Set up Action Bar
val navController = host.navController
setupActionBar(navController)
//...
}
private fun setupActionBar(navController: NavController) {
drawerLayout = findViewById(R.id.drawer_layout)
NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
val retValue = super.onCreateOptionsMenu(menu)
val navigationView = findViewById<NavigationView>(R.id.nav_view)
// The NavigationView already has these same navigation items, so we only add
// navigation items to the menu here if there isn't a NavigationView
if (navigationView == null) {
menuInflater.inflate(R.menu.menu_overflow, menu)
return true
}
return retValue
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
// Have the NavHelper look for an action or destination matching the menu
// item id and navigate there if found.
// Otherwise, bubble up to the parent.
return NavigationUI.onNavDestinationSelected(item,
Navigation.findNavController(this, R.id.my_nav_host_fragment))
|| super.onOptionsItemSelected(item)
}
override fun onSupportNavigateUp(): Boolean {
return NavigationUI.navigateUp(drawerLayout,
Navigation.findNavController(this, R.id.my_nav_host_fragment))
}
}
Then in the fragment file you can inflate further menu items. In the codelab, main_menu.xml contains a shopping cart item which is added to the overflow setup in the main activity above.
MainFragment.kt
class MainFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
setHasOptionsMenu(true)
return inflater.inflate(R.layout.main_fragment, container, false)
}
override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {
inflater?.inflate(R.menu.main_menu, menu)
}
}

Resources