Is it possible to create a Navigation Fragment to contain my navigation back button click logic.
Multiple Fragment's that have a back button would then be able then inherit from the Navigation Fragment.
I'm new to Kotlin development. As you see below the SigninFragment inflates the view, I'm not sure how to get a reference to the view & back button in a parent Navigation Fragment
class SigninFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_signin, container, false)
// Navigation back button logic
var headerBackButton = view.findViewById<ImageButton>(R.id.headerBackButton)
headerBackButton.setOnClickListener {
val navController = NavHostFragment.findNavController(this#SignInFragment)
navController.navigateUp()
}
return view
}
}
I'm not sure if I got your problem right but could this be the trick?
open class NavigationFragment() : Fragment() {
fun asignNavigationBackClickListener(backButton: View) {
backButton.setOnClickListener {
val navController = NavHostFragment.findNavController(this#NavigationFragment)
navController.navigateUp()
}
}
}
class SigninFragment : NavigationFragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_signin, container, false)
asignNavigationBackClickListener(view.findViewById(R.id.headerBackButton))
return view
}
}
I think you can use this code for going back to the previous activity:
headerBackButton.setOnClickListener {
finish()
}
Related
I have a button, which onClicked takes to another activity. Now I want the activity to open as bottomDialog. How o do that??
if (success) {
val intent = Intent(this, PaymentActivity::class.java)
intent.putExtra("total_amount",totalAmount)
startActivity(intent)
finishAffinity()
}
I want the PaymentActivity mentioned here to be opened as bottomSheet.
change your Payment Activity to BottomSheetDialogFragment
class PaymentActivity : BottomSheetDialogFragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.activity_payment, container, false)
}
}
and you can call it like this,
if (success) {
val b = PaymentActivity()
b.show(supportFragmentManager, "Hi")
}
Add total amount in Bundle.
I am looking to send a string from FragmentA to FragmentDialog by means of an interface Communicator through my Main Activity (which automatically opens FragmentDialog). Some code to illustrate what I have:
class FragmentA: Fragment() {
private lateinit var communicator: Communicator
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_A, container, false)
val title = "My Title"
button.setOnClickListener { communicator.passData(title) }
return view
}
}
class FragmentDialog : DialogFragment() {
var title: String? = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.dialog, container, false)
view.button_close.setOnClickListener { dismiss() }
title = arguments?.getString("title")
view.dialog_title.text = title
return view
}
}
interface Communicator {
fun passData (titleInput: String)
}
and in my Main Activity, which extends the interface:
private val fragmentDialog = FragmentDialog()
override fun passData(titleInput: String) {
val bundle = Bundle()
bundle.putString("title", titleInput)
fullScreenDialog.arguments = bundle
val transaction = supportFragmentManager.beginTransaction().add(android.R.id.content, fragmentDialog).addToBackStack(null).commit()
}
When I click the button in FragmentA, I want to send the string title to Communicator, set the XML element with id dialog_title in FragmentDialog and automatically open the fragment. In fact, this works fine. The issue is that my "back button" in FragmentDialog, i.e. view.button_close.setOnClickListener { dismiss() } only works exactly once. If I click button in FragmentA again, FragmentDialog opens fine with the correct title still but button_close no longer dismisses the dialog. What could be causing this? I am not getting any consol errors.
Guys I need your help.
I use android navigation component and want to save backstack after user press button and restore it after. I found 2 methods
navController.saveState(): Bundle and navController.restoreState(bundle: Bundle).
But i have problem in use it. Seems like saveState work greate (i see bundle, and backstack inside), but i dont understand how to use restoreState, because the documentation says:
Restores all navigation controller state from a bundle. This should be called before any call to setGraph.
https://developer.android.com/reference/kotlin/androidx/navigation/NavController#restorestate
Okay, i did it, seems like backstack restored, but on screen i see first fragment (instead of the one I had when I saved it). What i do wrong?
Code:
FirstFragment
private val TAG = this::class.java.name
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_first, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
btn_forward.setOnClickListener { findNavController().navigate(R.id.action_firstFragment_to_secondFragment) }
btn_back.setOnClickListener { requireActivity().onBackPressed() }
}
}
SecondFragment
class SecondFragment : Fragment() {
private val TAG = this::class.java.name
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_second, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
btn_forward.setOnClickListener { findNavController().navigate(R.id.action_secondFragment_to_thirdFragment) }
btn_back.setOnClickListener { requireActivity().onBackPressed() }
}
}
ThirdFragment
class ThirdFragment : Fragment() {
private val TAG = this::class.java.name
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_third, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
btn_finish.setOnClickListener {
(requireActivity() as MainActivity).saveState() //here save bundle
requireActivity().finishAfterTransition()
}
btn_back.setOnClickListener { requireActivity().onBackPressed() }
}
}
MainActivity
class MainActivity : AppCompatActivity() {
private val TAG = "MySuperActivity"
lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d(TAG, "onCreate($savedInstanceState) called")
initNavController()
}
private fun initNavController() {
val navHostFragment = nav_host_fragment as NavHostFragment
val graphInflater = navHostFragment.navController.navInflater
val graph = graphInflater.inflate(R.navigation.main_graph)
navController = navHostFragment.navController
navHostFragment.childFragmentManager
if (App.instance.savedBundle != null) {
Log.d(TAG, "bundle: ${App.instance.savedBundle}")
navController.restoreState(App.instance.savedBundle)
graph.startDestination = R.id.thirdFragment
}
navController.graph = graph
Log.d(TAG, "navController.currentDestination: ${navController.currentDestination}")
Log.d(TAG, "navController.graph.startDestination: ${navController.graph.startDestination}")
}
fun saveState(){
App.instance.savedBundle = navController.saveState()
Log.d(TAG, "saveState() : ${App.instance.savedBundle}")
}
}
here some logs: logs
full code:github
I am not sure if my answer helps you, but I had many issues trying to save the navigation state from handling rotations. The issue that I had comes from an old version of the navigation component, I update to the most recent, and it fixes the issue:
def android_navigation = '2.3.4'
implementation "android.arch.navigation:navigation-fragment-ktx:$android_navigation"
implementation "android.arch.navigation:navigation-ui-ktx:$android_navigation"
implementation "androidx.navigation:navigation-dynamic-features-fragment:$android_navigation"
Lets suppose I have a fragment class like this one below named EmailSearchResultPageFragment().
class EmailSearchResultPageFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_email_search_result_page, container, false)
someFunctionThatRequiresViewFromOnCreate(view)
showPage(fragment)
return view
}
fun someFunctionThatRequiresViewFromOnCreate(view: View) {
view.DoSomethingWithView(view)
}
fun showPage(fragment: Fragment) {
fragmentManager!!.beginTransaction().addToBackStack(null).replace(R.id.container, fragment).commit()
}
}
If i want to unit test someFunctionThatRequiresViewFromOnCreate(:), and showPage(:) what would be the best practice ??
How can I possibly mock view and fragment that is being instantiated inside onCreate???!
class HomeFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
val v = inflater.inflate(R.layout.fragment_home, container, false)
val fab = v.findViewById(R.id.fab) as FloatingActionButton
fab.setOnClickListener {
val blankFragment = BlankFragment()
val manager = childFragmentManager
manager.beginTransaction().replace(R.id.frame_container, blankFragment, blankFragment.tag).commit()
// System.out.println("You have reached the floating action button")
}
return v
}
}
Getting a no view found error. I may have issues with the R.id.frame_content but Kotlin doesn't immediately identify all id values...
This may not be the best way and I apologize for the horrible formatting I does this while answer out on my phone and it's hard to get it perfect. Anyways In the activity that holds your fragments, such as MainActivity.kt add...
supportFragmentManager.beginTransaction.add(R.id.fragment_home, HomeFragment.newInstance, "HomeFragment").commit()
In your HomeFragment.kt change the following.
class HomeFragment: Fragment(){
companion object {
fun newInstance(): HomeFragment{
return HomeFragment() }
fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view: View = inflater!!.inflate(R.layout.fragment_home, container, false)
val activity = activity
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val clickListener = View.OnClickListener { view ->
when (view.getId()) {
R.id.fab -> NextFragment()
}
}
fab.setOnClickListener(clickListener)
}
NextFragment(){
val fragManager = activity?.supportFragmentManager
fragManager?.beginTransaction()?.replace(R.id.frame_container, BlankFragment.newInstance(), "blankFragment")?.commit()
}
Make sure you make the same changes to BlankFragment.kt
class BlankFragment: Fragment(){
companion object {
fun newInstance(): BlankFragment{
return BlankFragment() }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view: View = inflater!!.inflate(R.layout.fragment_blank, container, false)
val activity = activity
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//Do some stuff with your views in BlankFragment here
}
}