How do I programmatically add a fragment to a fragment in kotlin - android

I have a fragment which is really a screen in my app... on this screen I want to load one of a number of fragments depending on the properties of a model which gets passed to the screen fragment. I'll do this using a conditional when... but first:
I can't actually even load a basic fragment. Here is the code for the screen fragment:
class EditCommandFragment : Fragment() {
private val args by navArgs<EditCommandFragmentArgs>()
private lateinit var fragContainer: ConstraintLayout
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
setHasOptionsMenu(true)
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_edit_command, container, false)
//get a reference to the container.
fragContainer = view.findViewById<ConstraintLayout>(R.id.command_edit_container)
// add one of the edit fragments //this doesn't work
childFragmentManager.beginTransaction().add(fragContainer, EditCommandUIFragment.newInstance()).commit()
return view
}
This error thrown is:
None of the following functions can be called with the arguments supplied:
public open fun add(p0: Fragment, p1: String?): FragmentTransaction defined in androidx.fragment.app.FragmentTransaction
public open fun add(p0: Int, p1: Fragment): FragmentTransaction defined in androidx.fragment.app.FragmentTransaction
but this makes no sense. In all the tutorials I can find you pass the container as p0 and then the class of the fragment you want to add as p1... but this is asking for a string or an int, what??
How do I correctly add my EditCommandUIFragment to the container?

To add a Fragment you need an instance of the Fragment, not its "view" (that's the Fragment's responsibility to manage).
Construct an instance of your "child" Fragment by using the "recommended methods":
val yourNewFragment = YourNewFragment.newInstance()
Obtain the container where you will place this...
val container = R.id.place_where_you_will_put_it
Perform the transaction:
childFragmentManager.beginTransaction().add(container, yourNewFragment, "A TAG or NULL")
Now, keep in mind, if you do use a Tag, it's sometimes good practice (this really depends on your app/lifecycle/etc.) to check if the fragment is not there already...
val frag = childFragmentManager.findFragmentByTag("The Tag You Used Above")
if (frag == null) {
// add it
}
You get the idea.
If you do not use a TAG" then you can use the other alternative...
val frag = childFragmentManager.findFragmentById(id of the container where the fragment is supposed to be, aka: R.id.place_where_you_will_put_it)
This can be used to, for example, replace(...) a fragment, instead of add.

I, in my project, to do something similar to yours I used viewPager2
In practice, in the parent fragment, you position it inside the xml file that acts as a view
<androidx.viewpager2.widget.ViewPager2 android:id="#+id/viewpager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="15dp"
app:layout_constraintTop_toBottomOf="#+id/tabs"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
/>
then create a class that extends from FragmentStateAdapter
class ViewPagerAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
private val mFragmentList = ArrayList<Fragment>()
private val mFragmentTitleList = ArrayList<String>()
fun addFrag(fragment: Fragment, title: String) {
mFragmentList.add(fragment)
mFragmentTitleList.add(title)
}
override fun getItemCount(): Int {
return mFragmentList.size
}
override fun createFragment(position: Int): Fragment {
return mFragmentList[position]
}
}
after inside the on-create of the fragment parent do the setup (see the documentation)
for example in my example to add the children Fragments just call
viewPagerAdapter.addFrag(MyFragment(), "Title")
or to say which fragment to show
viewpager.currentItem = 5

Related

Should I inflate the layout in onCreateView or onViewCreated?

I am using the following fragment to show an onboarding screen on the first launch of the application. Should I inflate my layout in onCreateView or in onViewCreated? I don't quite understand how to decide on this. Also, do I need to create a ViewModel for my code?
class OnBoardingFragment : Fragment() {
private lateinit var viewPager: ViewPager
private lateinit var dotsLayout: LinearLayout
private lateinit var sliderAdapter: SliderAdapter
private lateinit var dots: Array<TextView?>
private lateinit var letsGetStarted: Button
private lateinit var next: Button
private lateinit var animation: Animation
private var currentPos: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val navOptions = NavOptions.Builder().setPopUpTo(R.id.onBoardingFragment, true).build()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_onboarding, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewPager = view.findViewById(R.id.slider);
dotsLayout = view.findViewById(R.id.dots);
letsGetStarted = view.findViewById(R.id.get_started_btn);
next = view.findViewById(R.id.next_btn)
sliderAdapter = SliderAdapter(requireContext())
viewPager.adapter = sliderAdapter;
addDots(0);
viewPager.addOnPageChangeListener(changeListener);
next.setOnClickListener {
viewPager.currentItem = currentPos + 1
}
letsGetStarted.setOnClickListener {
findNavController().navigate(R.id.action_onBoardingFragment_to_loginFragment)
}
}
private fun addDots(position: Int) {
dots = arrayOfNulls(2)
dotsLayout.removeAllViews();
for (i in dots.indices) {
dots[i] = TextView(requireContext())
dots[i]!!.text = HtmlCompat.fromHtml("•", HtmlCompat.FROM_HTML_MODE_LEGACY)
dots[i]!!.setTextColor(
ContextCompat.getColor(
requireContext(),
android.R.color.darker_gray
)
)
dots[i]!!.textSize = 35F
dotsLayout.addView(dots[i])
}
if (dots.isNotEmpty()) {
dots[position]!!.setTextColor(
ContextCompat.getColor(
requireContext(),
R.color.wine_red
)
)
}
}
private var changeListener: ViewPager.OnPageChangeListener =
object : ViewPager.OnPageChangeListener {
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {
}
override fun onPageSelected(position: Int) {
addDots(position)
currentPos = position
animation =
AnimationUtils.loadAnimation(requireContext(), android.R.anim.fade_in)
if (position == 0) {
letsGetStarted.visibility = View.INVISIBLE
next.animation = animation
next.visibility = View.VISIBLE
} else {
letsGetStarted.animation = animation
letsGetStarted.visibility = View.VISIBLE
next.visibility = View.INVISIBLE
}
}
override fun onPageScrollStateChanged(state: Int) {}
}
}`
The Android framework calls Fragment's onCreateView to create the view object hierarchy. Therefore, it's correct to inflate the layout here as you did.
onViewCreated is called afterwards, usually you find views and setup them. So, your code is ok.
Regarding the ViewModel, in your sample code you're just configuring the UI so you won't need it. If instead, you need to obtain some data from an API service, transform it, show the states of "loading data", "data retrieved" and "there was an error retrieving data", then you would like not to do those things in the fragment and you could consider using an MVVM approach.
Some references:
https://developer.android.com/guide/fragments/lifecycle#fragment_created_and_view_initialized
https://guides.codepath.com/android/Creating-and-Using-Fragments
https://developer.android.com/topic/architecture
onCreateView is where you inflate the view hierarchy, and return it (so the Fragment can display it). If you're handling that inflation yourself, you need to override onCreateView so you can take care of it when the system makes that request. That's why it's named that way - when the view (displayed layout) is being created, this function is called, and it provides a View.
onViewCreated is called after the Fragment's view has already been created and provided to it for display. You get a reference to that view passed in, so you can do setup stuff like assigning click listeners, observing View Models that update UI elements, etc. You don't inflate your layout here because it won't be displayed (unless you're explicitly inflating other stuff and adding it to the existing view for some reason, which is more advanced and probably not what you're talking about).
So onCreateView is really concerned with creating a view hierarchy for display, and onViewCreated is for taking that displayed hierarchy and initialising your stuff. You might not need to implement onCreateView at all (e.g. if you use the Fragment constructor that takes a layout ID, so it sets it up for you) in which case you'd just implement onViewCreated instead. Or if you are handling it yourself in onCreateView, and you don't have much setup code, you might run that on the View you've inflated before you return it, and not bother with onViewCreated at all.
It's worth getting familiar with the Fragment lifecycle if you haven't already, just so you know the basic way the system moves between states and the callbacks it calls as it does so (and have a look at the documentation for the callback methods too!)

How can I start a Fragment from within another Fragment that's part of a viewPager?

I'm just getting into Android development in Kotlin, and I'm not quite sure what the correct approach to this situation is.
Essentially what I'm trying to do is have a ViewPager that scrolls through a list of items of the same type. The view associated with each item has a button. When I press the button, I would like to launch a new fragment, let's call it iteminfo. The iteminfo fragment should use the back stack so that you can hit the back button and return to the item view you were looking at before you hit the button.
The way I have it set up right now is this:
-My MainActivity add()s my ViewPager-containing fragment to the FragmentManager. (I am going for a one-activity architecture.)
-My ViewPager fragment contains a private inner class with a PagerAdapter that maintains a list of item fragments. The item fragments are generated and added to the PagerAdapter when the ViewPager fragment is created.
-The item fragments each have their own button. When the fragment is created, the button gets an onClickListener.
This is where I run into a bit of a dead end...
So far I've managed to get the iteminfo fragment to display and the item fragment to disappear, but my method was pretty janky: I performed a fragment transaction to add() the iteminfo fragment to the root element of my item_fragment.xml and then just set the visibility of the other elements in there to GONE. Cosmetically this gets me there, however if I hit the back button, the app just exits. So this is not going to cut it.
I have read that when switching between fragment views it is a good design to use a single empty FrameLayout in the .xml file as a fragment container and then add/remove fragments from that using the FragmentManager. This makes sense to me...
However, I am not sure how to remove() a fragment that is inside of a ViewPager. AFAIK you can't set a fragment's tag unless you're doing a fragment transaction. So if I want to perform replace(), I don't know how to reference the fragment I'm replacing.
Just as a Hail-Mary, I tried just replacing the whole ViewPager fragment (since this is added in MainActivity via a fragment transaction, I was able to tag it), but for some reason that just yields a NPE. It makes sense that a child fragment should not be able to replace its parent fragment, although I'm not 100% sure why it's a NPE.
Anyways, any enlightenment you can ship my way is greatly appreciated.
Edit: Added relevant code below.
MainActivity.kt:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val currentFragment = supportFragmentManager
.findFragmentById(R.id.fragment_container)
if (currentFragment == null) {
val fragment = ItemPagerFragment.newInstance()
supportFragmentManager
.beginTransaction()
.add(R.id.fragment_container, fragment, "viewPager")
.commit()
}
}
}
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
ItemPagerFragment.kt:
class ItemPagerFragment: Fragment() {
private lateinit var viewPager: ViewPager
private lateinit var pagerAdapter: ItemPagerAdapter
private val itemListViewModel: ItemListViewModel by lazy {
ViewModelProviders.of(this).get(ItemListViewModel::class.java)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_item_pager, container, false)
viewPager = view.findViewById(R.id.item_viewpager) as ViewPager
pagerAdapter = ItemPagerAdapter(getChildFragmentManager())
val items = itemListViewModel.items
for (item in items){
pagerAdapter.addFrag(ItemFragment.newInstance(item))
}
viewPager.adapter = pagerAdapter
return view
}
private inner class ItemPagerAdapter(fm: FragmentManager)
: FragmentStatePagerAdapter(fm)
{
private val itemFragmentList: MutableList<ItemFragment> = ArrayList()
override fun getItem(i: Int): Fragment {
return itemFragmentList[i]
}
override fun getCount() = itemFragmentList.size
fun addFrag(f: ItemFragment){
itemFragmentList.add(f)
}
}
companion object {
fun newInstance(): ItemPagerFragment {
return ItemPagerFragment()
}
}
}
fragment_item_pager.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.viewpager.widget.ViewPager
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/item_viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
ItemFragment.kt:
class ItemFragment(i: Item): Fragment() {
private var item: Item = i;
private lateinit var infoButton: Button
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_item, container, false)
infoButton = view.findViewById(R.id.info_button) as Button
infoButton.setOnClickListener{
val fragment: InfoFragment = InfoFragment.newInstance(item)
val fragmentManager: FragmentManager = childFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()
// this is where I have been having no success
// and know my approach is certainly wrong/confused
fragmentTransaction.replace(R.id.item_frame, fragment)
fragmentTransaction.addToBackStack(null)
fragmentTransaction.commit()
}
return view
}
companion object {
fun newInstance(i: Item): ItemFragment {
return ItemFragment(i)
}
}
}
fragment_item.xml:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/item_frame"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp">
<Button
android:id="#+id/info_button"
android:layout_gravity="center|bottom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/some_string"
/>
</FrameLayout>
InfoFragment.kt and fragment_info.xml could be anything, it doesn't matter what they are for the purposes of my question.
If I understand your problem correctly, the solution here is quite simple.
You have an Activity in which there is a FrameLayout, which is a container for fragments. Actually in it you put your fragment with ViewPager. When you click on the button, you simply put your new fragment on top of the fragment with ViewPager.
The bottom line is that all actions must occur through Activity. Those. you must have an interface that your Activity will implement, and the fragments will receive its instance from the context (for example, in the fragment's onAttach () method). When you click on your button, you simply pass your event to the Activity. And Activity is already creating a new fragment, thereby preserving the backstack.
It is important to understand that when working with fragments, they should not know anything about each other's existence. All operations must occur through abstraction and be performed by your parentView (Activity, ParentFragmen, etc.)

I can't find fragment by its ID nor its tag

i am currently programming an app in Android Studio and i am having a big issue. The main problem is, that i want an activity with a fragment in it and this fragment has got a spinner. I wanted to find the spinner by id, but it always returned null and i read that i can't use findViewById if it is not in the ContentView i just set. So i am currently trying to find the fragment that contains the spinner, but i also can't find the fragment, i tried findFragmentById and findViewById from the FragmentManager. I always get a TypeCastException and if i try findFragmentById(...)!! it throws a NullPointer.
This is my MainActivity:
class MainActivity : AppCompatActivity() {
private val manager: FragmentManager? = supportFragmentManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
showDeviceFragment()
val fragment = manager!!.findFragmentById(R.id.fragment_holder) as DeviceFragment
val options = arrayOf("Wandhalterung (Arm)", "Gestellhalterung (Arm)", "Gestellhalterung")
fragment.option.adapter = ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, options)
}
fun showDeviceFragment() {
val transaction = manager!!.beginTransaction()
val fragment = DeviceFragment()
transaction.add(R.id.fragment_holder, fragment, "DEVICE_FRAGMENT")
transaction.addToBackStack(null)
transaction.commit()
}
}
And this is the DeviceFragment:
class DeviceFragment : Fragment() {
lateinit var option : Spinner
companion object {
fun newInstance(): DeviceFragment {
return DeviceFragment()
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_device, container, false)
option = view.spinner
return inflater?.inflate(R.layout.fragment_device, container, false)
}
}
The fragment_holder just is a FrameLayout.
Thanks in advance
Few things:
1) No need to hold reference for supportFragmentManager, use it directly (because i am not sure if it will be null when Activity is initialized)
2) Try removing addToBackStack(null) and using findFragmentByTag("DEVICE_FRAGMENT")
3) Most importantly, Don't try to access "things" of Fragment from Activity, do those Adapter initialization/fill in the Fragment itself. Because Fragment has its own lifecycle and you may try to access "things" at wrong lifecycle

TextView Id keeps calling on null no matter what way I try to change its content

I have a TabLayout with 3 fragments inside. Fragment 0 contains details, 1 is a description and 2 is location.
But no matter how I try to call the fragment Ids inside my carDetailsActivity or I try to pass the value from the activity to the fragments it keeps calling on null and giving the following result:
Tab Fragment
Now I tried multiple ways one of them to Bundle the text value from the activity to the fragment but I still got null "IllegaleStateException". (which was the best option to my knowledge) but it still didnt work.
This is how I want it to look (the design is in arabic):
Here is my Activity where im calling them at the moment but I just get empty strings (had to give them ? because the app kept crashing if I didnt):
class CarDetailsActivity : BaseActivity() {
val TAG = "CarDetailsActivity"
override fun onCreate(savedInstanceState: Bundle?) {
val car = intent.extras!!.getParcelable<Car>(DETAILS_TRANSFER) as Car
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_car_details)
activateToolbar(true)
carPriceDetails?.text = car.price.toString()
carDate?.text = car.date
carCategory?.text = car.category
carModel?.text = car.brandModel
carYear?.text = car.modelYear
carKilometer?.text = car.kilometer.toString()
carGear?.text = car.gearType
carFuel?.text = car.fuelType
val fragmentAdapter = FragmentCarInfo(supportFragmentManager)
viewPager.adapter = fragmentAdapter
tabLayout.setupWithViewPager(viewPager)
carBrand.text = car.brand
carModel.text = car.brandModel
carYear.text = car.modelYear
Picasso.with(this).load(car.image)
.error(R.drawable.ic_cars)
.placeholder(R.drawable.ic_cars)
.into(carImage)
}
}
Here is the main Fragment that contains that layout that contains the details:
class InfoFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_car_details_info, container, false)
}
}
I have asked this similar question before but it was'nt clear enough so I hope this was a better way to post my problem.
-EDIT
My tabLayout viewPager uses this
class FragmentCarInfo (fm : FragmentManager) : FragmentPagerAdapter(fm){
override fun getItem(position: Int): Fragment {
return when (position) {
0-> {
InfoFragment()
}
1 -> {
AboutFragment()
}
else -> {
return LocationFragment()
}
}
}
override fun getCount(): Int {
return 3
}
override fun getPageTitle(position: Int): CharSequence? {
return when (position) {
0-> "Details"
1-> "About"
else -> {
return "Locations"
}
}
}
}
First of all, I would recommend ditching the old and nasty TabLayout and switch to the new ViewPager2 instead of the old ViewPager. UI wise, instead of the TabLayout, create custom 3 buttons inside a LinearLayout or ConstraintLayout.
Now, in your case I would use the ViewPager2 with a FragmentStateAdapter because it was designed to work well with Fragments and you can create as many of them as you need. I even had a project where I had 80 fragments created with a FragmentStateAdapter.
Second of all, you need a newInstance constructor for your fragments as a safe pattern. If you don't use that, you might get some exceptions later in your app, but other than that you will also use the function to pass your data to each fragment.
Here is how I do it:
class FragmentStateAdapter(
fragmentManager: FragmentManager,
lifecycle: Lifecycle,
car: Car
) : FragmentStateAdapter(fragmentManager, lifecycle) {
override fun getItemCount(): Int = 3
override fun createFragment(position: Int): Fragment {
return when (position) {
0 -> InfoFragment.newInstance(car)
1 -> AboutFragment.newInstance(car)
else -> LocationFragment.newInstance(car)
}
}
Basically I am passing your car object (presuming it's the only data object that needs to reach the fragments) as a parameter to the FragmentStateAdapter, locking the itemCount to 3 because you have 3 fragments and then returning an instance of each fragment in the override method createFragment(). Now, creating the instance functions:
class InfoFragment : Fragment() {
companion object {
private const val BUNDLE_KEY = "car_object"
fun newInstance(car: Car) = InfoFragment().apply {
arguments = Bundle().apply {
putParcelable<Car>(BUNDLE_KEY, car)
}
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_car_details_info, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val car = arguments.getParcelable<Car>(BUNDLE_KEY)
}
}
And this is how you get your car object inside your fragments. Basically in the newInstance() function you create a new instance of your fragment and pass data to it via bundle. Inside your activity this is how you initialise your adapter:
val adapter = FragmentStateAdapter(supportFragmentManager, lifecycle, car)
Now, about the replacement for the tab view, do a custom LinearLayout like this:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="1.0">
<Button
android:id="#+id/button1"
style="#style/Widget.AppCompat.Button.Borderless"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.333"
android:minWidth="0dp"
android:minHeight="0dp"
android:paddingTop="14dp"
android:paddingBottom="14dp"
android:text="Details"
android:textAllCaps="true" />
<Button
android:id="#+id/button2"
style="#style/Widget.AppCompat.Button.Borderless"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.333"
android:minWidth="0dp"
android:minHeight="0dp"
android:paddingTop="14dp"
android:paddingBottom="14dp"
android:text="About"
android:textAllCaps="true" />
<Button
android:id="#+id/button3"
style="#style/Widget.AppCompat.Button.Borderless"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.333"
android:minWidth="0dp"
android:minHeight="0dp"
android:paddingTop="14dp"
android:paddingBottom="14dp"
android:text="Locations"
android:textAllCaps="true" />
and then set the initial color of the buttons after your own preference with android:background="#COLOR_OR_CHOICE"
In activity, add the three buttons in an ArrayList of buttons and use this listener on the viewPager2:
viewPager2.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
// here, get the position of the viewPager2 and change the color of the current button using the arrayList index that matches the viewPager2 position.
})
Then, on every button's click listener, do viewPager2.setCurrentItem(position_you_want_to_go_to, true) where true is a check for a smooth scroll.
Hope this helps.
Shouldn't you be assigning the values to the fields in your fragment?

How to change Fragment Kotlin

I'm starting in Kotling and I don't know how to change between fragments, I have tried this code:
val manager = supportFragmentManager
val transaction = manager.beginTransaction()
transaction.add(R.layout.fragment_information.toInt(), ComplainFragment())
transaction.commit()
R.layout.fragment_information.toInt()
But i have an error with this parameter because it doesn't find the fragment Id.
I usually use replace to change between fragments. Also change R.layout.fragment_information to R.id.fragment_layout_id only, so no need toInt()
transaction.replace(R.id.fragment_layout_id, fragment)
Here is my suggestion.
var fragment: Fragment? = null
when (itemId) {
R.id.fragment_information -> {
fragment = ComplainFragment()
}
}
if (fragment != null) {
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragment_layout_id, fragment)
transaction.commit()
}
The other answers will work but still we can improve a lot by using extension functions in Kotlin.
Add an extension function to the FragmentManager class like below,
inline fun FragmentManager.doTransaction(func: FragmentTransaction.() ->
FragmentTransaction) {
beginTransaction().func().commit()
}
then create an extension function to the AppCompatActivity class,
fun AppCompatActivity.addFragment(frameId: Int, fragment: Fragment){
supportFragmentManager.doTransaction { add(frameId, fragment) }
}
fun AppCompatActivity.replaceFragment(frameId: Int, fragment: Fragment) {
supportFragmentManager.doTransaction{replace(frameId, fragment)}
}
fun AppCompatActivity.removeFragment(fragment: Fragment) {
supportFragmentManager.doTransaction{remove(fragment)}
}
Now, to add and remove fragments from any activity, you just need to call like this,
addFragment(R.id.fragment_container, fragment)
replaceFragment(R.id.fragment_container, fragment)
please refer the below link for more info,
https://medium.com/thoughts-overflow/how-to-add-a-fragment-in-kotlin-way-73203c5a450b
This is an example for you to go to a fragment or activity by clicking a button inside another fragment:
class Fragment_One: Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_one, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
btn_goToActivity2.setOnClickListener {
val intent = Intent(context, SecondActivity::class.java)
startActivity(intent)
}
btn_goToFragment2.setOnClickListener {
var fr = getFragmentManager()?.beginTransaction()
fr?.replace(R.id.fragment, Fragment_Two())
fr?.commit()
}
}
}
When you add a fragment, you need to add it to an ID that exists in your Activity's layout, not an entire layout:
supportFragmentManager.beginTransaction().add(R.id.some_id_in_your_activity_layout, ComplainFragment()).commit()
In case anyone still needs a quick approach to this. I created a function than can be easily called whenever you need to change a fragment.
private fun replaceFragment(fragment: Fragment) {
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.frame, fragment)
transaction.commit()
}
R.id.frame in this case is the id of my Framelayout in the activity that will hold my fragment. All you have to do now is call the function.
replaceFragment(HomeFragment())
private fun transitionFragment(fragment: Fragment) {
val transition = requireActivity().supportFragmentManager.beginTransaction()
transition.replace(R.id.fragment_container_create_void_parent, fragment)
.addToBackStack(null).commit()
}
fragment-ktx jetpack library contains convenient extension functions which simplify many things, including transactions:
// MyActivity.kt
class MyActivity : AppCompatActivity() {
...
fun showMyFragment() {
val fragment = MyFragment()
supportFragmentManager.commit {
replace(R.id.fragment_container, fragment)
}
}
}
R.id.fragment_container it's an id of a fragment container in the parent layout. There's FragmentContainerView which is the recommended container, for example:
<!-- my_activity_layout.xml -->
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout ...>
<androidx.fragment.app.FragmentContainerView
android:id="#+id/fragment_container"
... />
...
</androidx.constraintlayout.widget.ConstraintLayout>
But if your purpose is to implement in-app navigation, it's better and much easier to use Navigation component instead of manually switching fragments.
this is my solution for Change current fragment to orther in kotlin:
val supportFragment = SupportFragment()
requireActivity().supportFragmentManager.beginTransaction()
.add(this.id, supportFragment)
.addToBackStack("ok")
.commit()

Categories

Resources