switch from one fragment to another in main activity - android

Example of what i'm trying to achieve https://i.stack.imgur.com/lhp10.png
I'm stuck on the part where i need to switch to createUserFragment from defaultFragment
i attached this to my button, but nothing happens when i press it, not sure what's wrong there
MainActivity
class MainActivity : AppCompatActivity() {
lateinit var appBarConfiguration: AppBarConfiguration
lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
navController = findNavController(R.id.hostFragment)
appBarConfiguration = AppBarConfiguration(navController.graph,drawer_layout)
NavigationUI.setupActionBarWithNavController(this, navController,drawer_layout)
NavigationUI.setupWithNavController(navigation_drawer,navController)
}
}
override fun onSupportNavigateUp(): Boolean {
return NavigationUI.navigateUp(navController,appBarConfiguration)
}
defaultFragment
class defaultFragment : Fragment() {
private lateinit var binding: defaultFragmentBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
binding = defaultFragmentBinding.inflate(inflater)
return (binding.root)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.createNewBlankFAB.setOnClickListener{
val transaction = (activity as MainActivity).supportFragmentManager.beginTransaction()
transaction.replace(R.id.defaultFragment, createUserFragment())
transaction.disallowAddToBackStack()
transaction.commit()
}
}
}
My questions are:
1)How can i fix my thing?
2)Will it work properly? I.e no memory leaks or some other funky stuff
3)Do i have to use another fragment for data input or maybe there's another way?
UPDATE
I stil have no idea how this works, but apparently i was replacing wrong fragment, i switched this R.id.defaultFragment in transaction.replace to R.id.hostFragment from which, i assume, all other fragments spawn, but now it just spawn on top of my existing fragment and the drawer button doesn't change its state, i guess i have to either do all of this differently or somehow pass to the drawer navigation information that current fragment was changed?

This is how we navigate within fragments in Navigation component library . We use navigate to then id of the destination Fragment which is defined in navgraph
binding.createNewBlankFAB.setOnClickListener{
Navigation.findNavController(view).navigate(R.id.createUserFragment);
}

I think you have to call "transaction.add" instead of "replace" since you are calling your fragment from an activity. Replace is called when you call a fragment from another fragment, I believe.

Related

ViewModel dont saved after rotation

I create ViewModel class at fragment, but viewModel is not saving after rotation - every time i got new Viewmodel instance. Where is problem?
Acrivity:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
supportFragmentManager.beginTransaction()
.addToBackStack(null)
.replace(R.id.main_container, VideoFragment())
.commit()
}
}
Fragment:
class VideoFragment: Fragment() {
lateinit var viewModel: VideoViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
viewModel = ViewModelProvider(this).get(VideoViewModel::class.java)
return inflater.inflate(R.layout.fragment_video, container, false)
}
}
ViewModel:
class VideoViewModel: ViewModel() {
init {
Log.i("XXX", "$this ")
}
}
if i will use "requireActivity()" - as ViewModelStoreOwner - viewModel isnt recreate, but its will bound to activity lifecycle.
viewModel = ViewModelProvider(requireActivity()).get(VideoViewModel::class.java)
This is because you are replacing your Fragment on every configuration change when the Activity is recreated. The FragmentManager already retains your Fragment for you. You should commit the transaction only on the initial creation:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.addToBackStack(null)
.replace(R.id.main_container, VideoFragment())
.commit()
}
}
}
Your problem is most likely caused due to the activity being destroyed and then created again after app is rotated.
To fix this you can give your fragment an id/tag when navigating and then when activity is rotated call your supportFragmentManager if there already exists an instance of your old fragment, if it does, navigate to old fragment instance, otherwise create a new instance just like you are doing now.
Wax911 (commented on 9 may 2020) answer to this question:
https://github.com/InsertKoinIO/koin/issues/693
He explains the problem with activities and their lifecycle when rotating the screen

How to run code just one time in Fragment on Android

In my application I have some fragments and for show this fragments I want use NavigationComponent.
I have one problem. When click on BottomNavigationItems and change fragments, run again fragment code!
I want just run codes just for first time!
My codes (for one of fragments) :
#AndroidEntryPoint
class HomeNewFragment : Fragment(), HomeContracts.View {
//Binding
private lateinit var binding: FragmentHomeNewBinding
#Inject
lateinit var presenter: HomePresenter
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = FragmentHomeNewBinding.inflate(layoutInflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//InitViews
binding.apply {
//Call apis
presenter.callApi()
}
}
}
For example when click on items, every time call this code : presenter.callApi()
Or when go to DetailFragment and when click on back, again call presenter.callApi()
How can I fix it?
You should replace your presenter.callApi() with fun onFragmentCreated triggered in your Presenter(ViewModel).
All you need is a boolean var to check its first time or not cause your Presenter(ViewModel) attatched to the activity, not the fragment so it can store your state.
Presenter {
fun onFragmentCreated() {
if(firstTime) callApi()

Fragments UI's overlapping

I'm creating a shopping app where the user can choose next product from the fragment. That will open the next fragment of the same class with more products and so on. I want to use swipe to dismiss behaviour, so when I swipe my finger down I dissmiss the current fragment. It's essential that while swiping the current fragment the user will be able to see the fragment underneath, so I can only use add instead of replace. Here's the problem: When I use add in my fragment manager, the created fragments UI's overlap, which makes the user swipe multiple fragments at once and not show swiping animation. The fragment uses one xml layout so it shares id across all fragments so I have no way of controlling which layouts are disabled. How do I make the user only swipe one fragment/disable touching fragments underneath? The swipe to dissmis is working well with replace but that's not the point. Here's my code.
Activity:
class PullDismissActivity : AppCompatActivity(), PullDismissLayout.Listener {
private lateinit var viewModel: StackingFragmentsViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_pulldismiss)
viewModel = ViewModelProvider(this).get(StackingFragmentsViewModel::class.java)
supportFragmentManager.beginTransaction()
.add(R.id.pull, SwipeFragment(), null)
.addToBackStack(null) // name can be null
.commit()
val layout = findViewById<PullDismissLayout>(R.id.pull)
layout.setListener(this)
}
override fun onDismissed() {
viewModel.popBackStack()
supportFragmentManager.popBackStack()
}
override fun onShouldInterceptTouchEvent(): Boolean {
return false
}
Fragment:
class SwipeFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val viewModel = ViewModelProvider(requireActivity()).get(StackingFragmentsViewModel::class.java)
viewModel.addFragment(this.hashCode())
val fragid = view.findViewById<TextView>(R.id.fragid)
fragid?.text = System.currentTimeMillis().toString()
val button = view.findViewById<Button>(R.id.button)
button.setOnClickListener {
requireActivity().supportFragmentManager.beginTransaction()
.add(R.id.pull, SwipeFragment(), null)
.addToBackStack(null)
.commit()
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment,container,false)
}

backstack with navigation component

using the navigation component on two fragments, my app currently destroys a fragment when navigateUp() is called (using the setupActionBarWithNavController()), and therefore when I enter the deleted fragment, all progress is lost, I am trying to change that my adding the fragment to a backstack (and only one instance of the fragment) but I've been struggling with that...
Here's some code:
MainActivity:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
loginRepository = LoginRepository()
if (!loginRepository.checkLoggedInState()) {
goToLoginActivity()
}
Log.d("FirebaseMain", "onCreate: ${loginRepository.checkLoggedInState()}")
//MyApplication.app.currentUser
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
navController = Navigation.findNavController(this, R.id.nav_host_fragment)
// setup custom action bar
setSupportActionBar(findViewById(R.id.toolbar))
// adds a return button in the ActionBar.
NavigationUI.setupActionBarWithNavController(this, navController)
}
FormFragment:
(the fragment I want to save its progress and add to backstack)
class FormFragment : Fragment() {
private lateinit var binding: FragmentFormBinding
private val checkListRecyclerAdapter = CheckListRecyclerAdapter(
hashMapOf(
0 to "mirrors",
1 to "blinkers1",
2 to "blinkers11",
3 to "blin4kers",
4 to "blink3e1123rs",
5 to "blink6ers",
6 to "blin53kers",
7 to "blin8kers",
8 to "blin7kers",
9 to "blin43kers",
10 to "blin32kers",
11 to "blin322kers",
)
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_form, container, false)
binding.rvCheckList.apply {
// this is used to load the data sequentially from top to bottom,
this.layoutManager = LinearLayoutManager(context)
// the adapter that loads the data into the list
this.adapter = checkListRecyclerAdapter
}
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
}
Thanks in advance for any help!
I've been struggling with some similar to this.
Fragments in Navigation Component doesnt keep their state. If you keep your data progress while navigate, you need create a SharedViewModel scoped to a Navigation Graph.
In Fragment:
private val navGraphScopedViewModel by navGraphViewModels<NavGraphViewModel>(R.id.your_nav_graph)
Create this class:
class NavGraphViewModel : ViewModel() {
var sharedData= CustomObject()
}
And from all your fragments you can access and set data:
val data = navGraphScopedViewModel.sharedData
I hope I've helped you

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

Categories

Resources