How to insert a fragment layout within a Frame Layout - android

I'm trying to create an Android app using Kotlin for the first time and am trying to use different fragment layouts when the user clicks on navigation icons without having them overlap my bottom navigation bar. I've created a FrameLayout with the intention of putting the fragment layouts within it, but am unclear on how to write the code to do so.
This is the MainActivity.kt that I have that covers the Nav bar now:
private val onNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.navigation_home -> {
setContentView(R.layout.fragment_home)
return#OnNavigationItemSelectedListener true
}
R.id.navigation_favorites -> {
setContentView(R.layout.fragment_favorite)
return#OnNavigationItemSelectedListener true
}
R.id.navigation_calculator -> {
setContentView(R.layout.fragment_calculator)
return#OnNavigationItemSelectedListener true
}
}
false
}
And this is my frame layout:
<FrameLayout
android:id="#+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above= "#id/nav_view">
</FrameLayout>

You want to navigate to the appropriate fragment when a tab on the NavBar is selected. This is how you do it.
Let's say we have a MainActivity which has 3 tabs: "Tab1", "Tab2", "Tab3"
The MainActivity will have 2 XML Layouts: The navBar and the FrameLayout.
Then, you're going to want to create 3 new Fragments (each having their own XML layout too). Those 3 Fragments will take the view of the framelayout when the appropriate navbar tab is selected
For Example:
MAIN ACTIVITY XML
<FrameLayout
android:id="#+id/mainFragment"
android:layout_width="match_parent"
android:background="#ffffff"
android:layout_height="match_parent"
android:layout_above="#+id/bottom_nav"
android:text="#string/title_home" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottom_nav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="?android:attr/windowBackground"
app:menu="#menu/bottom_navigation" />
MAIN ACTIVITY KOTLIN
class MainActivity : AppCompatActivity() {
private lateinit var manager: FragmentManager
private lateinit var transaction: FragmentTransaction
private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.navigation_tab1 -> {
title = "Tab1"
manager = supportFragmentManager
transaction = manager.beginTransaction()
transaction.replace(R.id.mainFragment, Tab1Fragment())
transaction.addToBackStack(null)
transaction.commit()
return#OnNavigationItemSelectedListener true
}
R.id.navigation_tab2 -> {
title = "Tab2"
manager = supportFragmentManager
transaction = manager.beginTransaction()
transaction.replace(R.id.mainFragment, Tab2Fragment())
transaction.addToBackStack(null)
transaction.commit()
return#OnNavigationItemSelectedListener true
}
R.id.navigation_tab3 -> {
title = "Tab 3"
manager = supportFragmentManager
transaction = manager.beginTransaction()
transaction.replace(R.id.mainFragment, Tab3Fragment())
transaction.addToBackStack(null)
transaction.commit()
return#OnNavigationItemSelectedListener true
}
}
false
}
#SuppressLint("CommitTransaction")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navigation = findViewById<View>(R.id.bottom_nav) as BottomNavigationView
navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener)
}
}

Please refer to very good source of knowledge: https://developer.android.com/training/basics/fragments/fragment-ui#AddAtRuntime
basically you need to create transaction which is mentioned in link:
val transaction = supportFragmentManager.beginTransaction().apply {
// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack so the user can navigate back
replace(R.id.fragment_container, newFragment)
addToBackStack(null)
transaction.commit()
}
so in your case:
when (item.itemId) {
var fragment: Fragment
R.id.navigation_home -> {
fragment = HomeFragment()
}
.
.
.
val transaction = supportFragmentManager.beginTransaction().apply {
replace(R.id.fragment_container, fragment)
addToBackStack(null)
transaction.commit()
you can also use use add instead of replace.
Please refer to this post to determine which method use:
Difference between add(), replace(), and addToBackStack()

Related

Cannot remove Fragment in Android

I have two fragments, named them "Red" and "Blue" and three buttons. The first one adds "Red" fragment and changes the background to red color, the second one adds "Blue" fragment and changes the background to blue color and the third one has to remove the current fragment and change the color to the previous fragment's color.
The problem is that when I click the remove button I see a toast that the fragment is removed but the layout doesn't change to the color of the previous fragment.
var fragment1 = FirstFragment()
var fragment2 = SecondFragment()
lateinit var binding: ActivityMainBinding
var counter: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.redBtn.setOnClickListener {
supportFragmentManager
.beginTransaction()
.add(R.id.placeHolder, fragment1)
.addToBackStack("Red")
.commit()
counter++
binding.count.text = "$counter"
}
binding.blueBtn.setOnClickListener {
supportFragmentManager
.beginTransaction()
.replace(R.id.placeHolder, fragment2)
.addToBackStack("Blue")
.commit()
counter++
binding.count.text = "$counter"
}
binding.removeBtn.setOnClickListener {
val fragmentInstance = supportFragmentManager.findFragmentById(R.id.placeHolder)
if(fragmentInstance is FirstFragment){
supportFragmentManager.beginTransaction()
.remove(FirstFragment())
.commit()
Toast.makeText(this, "Removed Red Fragment", Toast.LENGTH_SHORT).show()
} else {
supportFragmentManager.beginTransaction()
.remove(SecondFragment())
.commit()
Toast.makeText(this, "Removed Blue Fragment", Toast.LENGTH_SHORT).show()
}
counter--
binding.count.text = "$counter"
}
}
}
It will not remove directly
make sure create object of First and Second Fragment
Then when you add or remove fragment from fragment manager use the same fragment object
Like you created
fragment1 and fragment2
Hello i think you have just to pass the framgents objects that aleady createed to be removed like this:
binding.removeBtn.setOnClickListener {
val fragmentInstance = supportFragmentManager.findFragmentById(R.id.placeHolder)
if(fragmentInstance is FirstFragment){
supportFragmentManager.beginTransaction()
.remove(fragment1)
.commit()
Toast.makeText(this, "Removed Red Fragment", Toast.LENGTH_SHORT).show()
} else {
supportFragmentManager.beginTransaction()
.remove(fragment2)
.commit()
Toast.makeText(this, "Removed Blue Fragment", Toast.LENGTH_SHORT).show()
}
counter--
binding.count.text = "$counter"
}
your problem is every click on remove btn will create new instance of fragment not the object already created

How can I start an activity with specific fragment

In my main activity I have bottom navigation bar. Each button opens a different fragment.My code in main activity looks like this
class MainActivity : AppCompatActivity() {
private val homeFragment = HomeFragment()
private val calendarFragment = CalendarFragment()
private val addFragment = AddFragment()
private val plannerFragment = PlannerFragment()
private val profileFragment = ProfileFragment()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
replaceFragment(homeFragment)
nav_view.setOnNavigationItemSelectedListener{
when(it.itemId){
R.id.homeButton -> replaceFragment(homeFragment)
R.id.calendarButton -> replaceFragment(calendarFragment)
R.id.addButton -> replaceFragment(addFragment)
R.id.plannerButton -> replaceFragment(plannerFragment)
R.id.profileButton -> replaceFragment(profileFragment)
}
true
}
}
private fun replaceFragment(fragment: Fragment){
if (fragment!=null){
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragmentContainer, fragment)
transaction.commit()
}
}
}
In profileFragment I have a button that opens a new activity called EditProfile. In that activity I have a button called goBackToProfileButton
I want to set a listener that will go back to mainactivity, but I want profileFragment to be open not the default fragment which is homeFragment.
goBackToProfileButton.setOnClickListener {
val intent = Intent(this,MainActivity::class.java)
startActivity(intent)
}
For now my code looks like this
You need to add some bundle data to your intent with information which fragment should be started
https://stackoverflow.com/a/819427/11538132
And then when you receive this bundle data then you can manually set your ** profileFragment**
You can add TAG while fragment transaction.
private fun replaceFragment(fragment: Fragment){
if (fragment!=null){
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragmentContainer, fragment, "FragmentTag")
transaction.commit()
}
}
While calling the first time to replace a fragment, find your fragment by tag.
//replaceFragment(homeFragment)
val fragment = supportFragmentManager.findFragmentByTag("FragmentTag")
if(fragment !=null){
replaceFragment(fragment)
} else{
replaceFragment(homeFragment)
}
if findFragmentByTag return null means no fragment was added. So add Home Fragment. If not null , it will update with last fragment added.
You could use fragment.startActivityForResult() and call the ProfileFragment from the MainActivity once it receives the onActivityResult() callback.

How to Open a fragment from another fragment using childFragmentManager?

My activity has a FrameLayout container for fragments and a BottomNavigationView to navigate between the fragments: Home, shop, account and cart. Navigating between these fragments works fine, but when navigating to new fragment (SignUpFragment) from AccountFragment when pushing the button 'signup' it crashes.
using supportFragmentManager didnt work like in MAinActivity so i am trying to use childFragmentManager instead, but the app crashes because it can not find the FrameLayout container in MainActivity.
MainAct:
class MainActivity : AppCompatActivity() {
lateinit var homeFragment: HomeFragment;
lateinit var shopFragment: ShopFragment;
lateinit var accountFragment: AccountFragment;
lateinit var cartFragment: CartFragment;
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
homeFragment = HomeFragment()
makeCurrentFragment(homeFragment)
btm_nav.setOnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.home -> {
homeFragment = HomeFragment()
makeCurrentFragment(homeFragment)
}
R.id.shop -> {
shopFragment = ShopFragment()
makeCurrentFragment(shopFragment)
}
R.id.account -> {
accountFragment = AccountFragment()
makeCurrentFragment(accountFragment)
}
R.id.cart -> {
cartFragment = CartFragment()
makeCurrentFragment(cartFragment)
}
}
true
}
}
fun makeCurrentFragment(fragment: Fragment) =
supportFragmentManager.beginTransaction().replace(R.id.frame_layout, fragment)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) //open = adds a new fragment to the stack
.commit()
}
Layout main:
<?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:background="#color/colorwhite"
tools:context=".MainActivity"
tools:ignore="ExtraText">
<FrameLayout
android:id="#+id/frame_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="#+id/btm_nav"
android:layout_marginBottom="12dp" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/btm_nav"
android:layout_alignParentBottom="true"
app:itemBackground="#color/colorwhite"
app:menu="#menu/bottom_nav"/>
</RelativeLayout>
AccountFragmenet:
class AccountFragment : Fragment() {
lateinit var signUpFragment: SignUpFragment;
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
btn_forgotPass.setOnClickListener {
Toast.makeText(
activity, "btn forgot pressed",
Toast.LENGTH_SHORT
).show()
}
btn_signIn_google.setOnClickListener {
Toast.makeText(
activity, "btn google pressed.",
Toast.LENGTH_SHORT
).show()
}
btn_login.setOnClickListener {
Toast.makeText(
activity, "btn login pressed.",
Toast.LENGTH_SHORT
).show()
}
/********** The problem is here ****/
btn_signup.setOnClickListener {
signUpFragment = SignUpFragment();
childFragmentManager.beginTransaction().replace(R.id.frame_layout, signUpFragment)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.commit()
}
}
}
Errormessage in logcat is :
java.lang.IllegalArgumentException: No view found for id 0x7f0800b7 (no.store.maast:id/frame_layout) for fragment SignUpFragment{b0a086e (7c1e4b61-3b07-48dd-b700-83748b0714c6) id=0x7f0800b7}
you can't access your frame layout in account fragment .
you must replace your fragment with your account fragment root layout .
btn_signup.setOnClickListener {
signUpFragment = SignUpFragment();
childFragmentManager.beginTransaction()
// give an id to your layout root account fragment
.replace(R.id.accountFragmentId, signUpFragment)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.commit()
}
Using ChildFragmentMAnager did not work on mine application no matter what I did, but i found a useful link that solved my problem:
How do i open fragment from fragment (kotlin)
My working code :
btn_signup.setOnClickListener {
signUpFragment = SignUpFragment();
val transaction = activity?.supportFragmentManager?.beginTransaction()
transaction?.replace(R.id.frame_layout, signUpFragment)
transaction?.addToBackStack("BackToSignInPage")
transaction?.commit()
}

BottomNavigationView OnNavigationItemSelectedListener doesn't triggered

I have this code in my BottomActivity.
private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.navigation_home -> {
Log.d("Navigation", "You are picking bottombar")
supportActionBar?.title = "Navigation Feed"
val dashboardFragment = DashboardFragment()
openFragment(dashboardFragment)
return#OnNavigationItemSelectedListener true
}
R.id.navigation_dashboard -> {
val homeFragment = HomeFragment()
openFragment(homeFragment)
return#OnNavigationItemSelectedListener true
}
R.id.navigation_notifications -> {
val notificationFragment = NotificationsFragment()
openFragment(notificationFragment)
return#OnNavigationItemSelectedListener true
}
}
false
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_bottom)
val navView: BottomNavigationView = findViewById(R.id.nav_view)
navView.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener)
val navController = findNavController(R.id.nav_host_fragment)
// 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)
private fun openFragment(fragment: Fragment) {
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.container, fragment)
transaction.addToBackStack(null)
transaction.commit()
}
All what it should do is setting listener and opening my fragments but it doesn't.
I think that problem is somewhere in mOnNavigationItemSelectedListener because
it even doesnt' log on console. My activity is loading just first fragment with text
This is home fragment and bottomview doesn't react on click. I need solution in kotlin language.
For navigation component to work correctly, you do not need BottomNavigationView.OnItemSelectedListener actually. In OnItemSelectedListener, loading fragments manually is exacly WRONG behaviour. If you really need it, you should do it in the following way:
bottomNav.setOnNavigationItemSelectedListener {
// ...
NavigationUI.onNavDestinationSelected(it, navController)
// ...
}
Check the ids of your menu items in menu_bottomnavigationview.xml.
Check the ids of your top level fragments in your navigation graphs, namely R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications.
These ids should be the same. Thats enough for navigation controller to load correct fragment into the NavHostFragment.
In addition to this, if you write BottomNavigationView.OnItemSelectedListener, then you overrided the default behaviour. Then navigation controller cannot load correct fragment into the NavHostFragment. If you really need to use BottomNavigationView.OnItemSelectedListener, then you should write the following code for loading fragments into NavHostFragment.
bottomNav.setOnNavigationItemSelectedListener {
NavigationUI.onNavDestinationSelected(it, navController)
}

Context is null in fragment before start of lifecycle?

There is a huge bug I am trying to figure out in my latest project using Kotlin. So how the app works is that it has a Bottom Navigation Activity, and on click on the icons on the Bottom menu, it replaces the container with the respected fragment. On onCreate of the activity, I instantiate all the different fragments with a function defined in a companionObject within the fragments, which returns itself (kind of like a static factory method in java):
override fun onCreate(savedInstanceState: Bundle?) {
{...}
navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener)
fragment1 = Fragment1.newInstance()
fragment2 = Fragment2.newInstance()
}
Then I have this switch to replace the container to the respected fragment on click:
private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.f1 -> {
replaceFragment(fragment1)
return#OnNavigationItemSelectedListener true
}
R.id.f2 -> {
replaceFragment(fragment2)
return#OnNavigationItemSelectedListener true
}
}
false
}
{...}
fun replaceFragment(destFragment: Fragment) {
supportFragmentManager
.beginTransaction()
.replace(R.id.container, destFragment)
.addToBackStack(destFragment.toString())
.commit()
}
The fragments are communicating. If you where to click on both fragments, and thereby cause their lifecycles to execute, the code works fine. However, if you do changes in frag1, invoking a communication between the fragments, without having clicked on frag2 in advance, the app crashes. The reason is this line in frag2:
fun saveList(){
val sharedpref : SharedPreferences = context!!.getSharedPreferences("sharedPrefs", MODE_PRIVATE)
{...}
}
Turns out that the context is null, if the user has never invoked the frag2 lifecycle by clicking it. Any ideas to how to fix this problem?
Use Activity context for these case
fun saveList() {
activity?.let {
val sharedpref: SharedPreferences =
it.getSharedPreferences("sharedPrefs", MODE_PRIVATE)
{ }
}
}

Categories

Resources