Android: Handle back button in nested fragments - android

I'm handling back button of device via fragments but while calling from nested fragments I'm facing issue. It always calls the backPressed method of MainActivity. I'm handling it as:
class MainActivity : BaseActivity() {
protected fun goToHome() {
findNavController(R.id.nav_host_fragment).navigate(R.id.global_action_pop_to_home)
}
override fun onBackPressed() {
if (supportFragmentManager.backStackEntryCount != 0) {
supportFragmentManager.popBackStackImmediate()
} else {
return super.onBackPressed()
}
}
}
Here I'm calling MainTabFragment which is having one more fragment container where I'm loading some child fragments as.
class MainTabFragment : BaseFragment() {
fun loadFragment(fragment: Fragment, addToBackStack: Boolean = false, arguments: Bundle? = null): Boolean {
//switching fragment
val fragmentTransaction: FragmentTransaction = childFragmentManager.beginTransaction()
if (addToBackStack) {
fragmentTransaction.add(R.id.main_container, fragment)
fragmentTransaction.addToBackStack(fragment.javaClass.name)
} else {
fragmentTransaction.replace(R.id.main_container, fragment)
}
fragment.arguments = arguments
fragmentTransaction.commit()
return true
}
private fun handleBack() {
val callback: OnBackPressedCallback =
object : OnBackPressedCallback(true /* enabled by default */) {
override fun handleOnBackPressed() {
view?.hideKeyboard()
}
}
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback)
}
}
This is the Child fragment what I'm adding in main_container fragment.
open class HomeFragment : BaseFragment() {
private fun handleBack() {
val callback: OnBackPressedCallback =
object : OnBackPressedCallback(true /* enabled by default */) {
override fun handleOnBackPressed() {
if (viewModel.isContact) {
showDiscardCredentialDialog()
} else {
// findNavController().popBackStack()
//Here f gets null when I press back button
val f: Fragment? = childFragmentManager.findFragmentById(R.id.main_container)
if (f is HomeFragment) // do something with f
childFragmentManager.popBackStackImmediate()
}
}
}
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback)
}
}
Here I want to handle back press from nested fragment and open the last added fragment. TIA.

Related

MainActivity cannot be cast to interface error when implementing interface from child fragment in parent fragment

I have this app in which I have a parent fragment, which has 2 child fragments in a ViewPager2 object.
One of the child fragments has an interface to communicate changes on its menu to the parent fragment.
The child fragment in question is TasksListFragment.kt and the parent fragment is TodayFragment.kt
When I try to initialize the interface in the child fragment onAttach() function, I get
FATAL EXCEPTION: main Process: com.rajchenbergstudios.hoygenda, PID:
java.lang.ClassCastException: com.rajchenbergstudios.hoygenda.ui.activity.MainActivity cannot be cast to com.rajchenbergstudios.hoygenda.ui.todaylists.taskslist.TasksListFragment$ChildFragmentListener
I don't understand why I get this error, and it says MainActivity, when the parent is a fragment which is the one implementing the interface in the first place, not the MainActivity.
I have everything set up correctly:
I have an interface in the child fragment
The interface is used on the child fragment onCreateMenu to pass the menu object to its interface function onFragmentMenuChanged(menu: Menu)
I override the child fragment's onAttach() and initialize the interface:
override fun onAttach(context: Context) {
super.onAttach(context)
childFragmentListener = context as ChildFragmentListener
}
I write a function called setListener() which is called from the parent fragment to pass its context this to the function parameter which assigns it to the childFragment listener
fun setListener(listener: ChildFragmentListener) {
this.childFragmentListener = listener
}
The parent fragment implements the child fragment listener as seen in the TodayFragment.kt file
Can you tell me what am I doing wrong or how to implement an interface to effectively communicate from child fragment back to its parent fragment?
TasksListFragment.kt
#ExperimentalCoroutinesApi
#AndroidEntryPoint
class TasksListFragment : Fragment(R.layout.fragment_child_tasks_list), TasksListAdapter.OnItemClickListener {
private val viewModel: TasksListViewModel by viewModels()
private lateinit var searchView: SearchView
private lateinit var childFragmentListener: ChildFragmentListener
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val binding = FragmentChildTasksListBinding.bind(view)
val tasksListAdapter = TasksListAdapter(this)
binding.apply {
tasksListRecyclerview.layoutTasksListRecyclerview.apply {
adapter = tasksListAdapter
layoutManager = LinearLayoutManager(requireContext())
setHasFixedSize(true)
}
ItemTouchHelper(object: ItemTouchHelper.SimpleCallback(0,
ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT){
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
val task = tasksListAdapter.currentList[viewHolder.adapterPosition]
viewModel.onTaskSwiped(task)
}
}).attachToRecyclerView(tasksListRecyclerview.layoutTasksListRecyclerview)
}
loadObservable(binding, tasksListAdapter)
loadTasksEventCollector()
loadMenu()
}
private fun loadObservable(binding: FragmentChildTasksListBinding, tasksListAdapter: TasksListAdapter) {
viewModel.tasks.observe(viewLifecycleOwner){ tasksList ->
binding.apply {
HGDAViewStateUtils.apply {
if (tasksList.isEmpty()) {
setViewVisibility(tasksListRecyclerview.layoutTasksListRecyclerview, visibility = View.INVISIBLE)
setViewVisibility(tasksListLayoutNoData.layoutNoDataLinearlayout, visibility = View.VISIBLE)
} else {
setViewVisibility(tasksListRecyclerview.layoutTasksListRecyclerview, visibility = View.VISIBLE)
setViewVisibility(tasksListLayoutNoData.layoutNoDataLinearlayout, visibility = View.INVISIBLE)
tasksListAdapter.submitList(tasksList)
}
}
}
}
}
/**
* TasksListViewModel.TaskEvent.ShowUndoDeleteTaskMessage: Stays in this class. It asks for components relevant to this class.
* TasksListViewModel.TaskEvent.NavigateToEditTaskScreen: Stays in this class. The method it overrides comes from task list adapter.
* TasksListViewModel.TaskEvent.NavigateToDeleteAllCompletedScreen: Stays in this class. Relevant to menu which is in this class.
* TasksListViewModel.TaskEvent.NavigateToDeleteAllScreen: Stays in this class. Relevant to menu which is in this class.
*/
private fun loadTasksEventCollector() {
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
viewModel.tasksEvent.collect { event ->
when (event) {
is TasksListViewModel.TaskEvent.ShowUndoDeleteTaskMessage -> {
Snackbar
.make(requireView(), "Task deleted", Snackbar.LENGTH_LONG)
.setAction("UNDO"){
viewModel.onUndoDeleteClick(event.task)
}
.show()
}
is TasksListViewModel.TaskEvent.NavigateToEditTaskScreen -> {
val action = TodayFragmentDirections
.actionTodayFragmentToTaskAddEditFragment(task = event.task, title = "Edit task", taskinset = null, origin = 1)
findNavController().navigate(action)
}
is TasksListViewModel.TaskEvent.NavigateToAddTaskToSetBottomSheet -> {
val action = TasksListFragmentDirections.actionGlobalSetBottomSheetDialogFragment(task = event.task, origin = 1)
findNavController().navigate(action)
}
is TasksListViewModel.TaskEvent.NavigateToDeleteAllCompletedScreen -> {
val action = TasksListFragmentDirections
.actionGlobalTasksDeleteAllDialogFragment(origin = 1)
findNavController().navigate(action)
}
is TasksListViewModel.TaskEvent.NavigateToDeleteAllScreen -> {
val action = TasksListFragmentDirections
.actionGlobalTasksDeleteAllDialogFragment(origin = 3)
findNavController().navigate(action)
}
}.exhaustive
}
}
}
private fun loadMenu(){
val menuHost: MenuHost = requireActivity()
menuHost.addMenuProvider(object: MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
childFragmentListener.onFragmentChanged(menu)
menuInflater.inflate(R.menu.menu_tasks_list_fragment, menu)
val searchItem = menu.findItem(R.id.tasks_list_menu_search)
searchView = searchItem.actionView as SearchView
val pendingQuery = viewModel.searchQuery.value
if (pendingQuery != null && pendingQuery.isNotEmpty()) {
searchItem.expandActionView()
searchView.setQuery(pendingQuery, false)
}
searchView.OnQueryTextChanged{ searchQuery ->
viewModel.searchQuery.value = searchQuery
}
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
menu.findItem(R.id.tasks_list_menu_hide_completed).isChecked =
viewModel.preferencesFlow.first().hideCompleted
}
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
return when (menuItem.itemId) {
R.id.tasks_list_menu_sort_by_date -> {
viewModel.onSortOrderSelected(SortOrder.BY_DATE)
true
}
R.id.tasks_list_menu_sort_by_name -> {
viewModel.onSortOrderSelected(SortOrder.BY_NAME)
true
}
R.id.tasks_list_menu_hide_completed -> {
menuItem.isChecked = !menuItem.isChecked
viewModel.onHideCompletedSelected(menuItem.isChecked)
true
}
R.id.tasks_list_menu_delete_completed -> {
viewModel.onDeleteAllCompletedClick()
true
}
R.id.tasks_list_menu_delete_all -> {
viewModel.onDeleteAllClick()
true
}
else -> false
}
}
}, viewLifecycleOwner, Lifecycle.State.RESUMED)
}
interface ChildFragmentListener {
fun onFragmentChanged(menu: Menu)
}
fun setListener(listener: ChildFragmentListener) {
this.childFragmentListener = listener
}
override fun onItemClick(task: Task) {
viewModel.onTaskSelected(task)
}
override fun onItemLongClick(task: Task) {
viewModel.onTaskLongSelected(task)
}
override fun onCheckboxClick(task: Task, isChecked: Boolean) {
viewModel.onTaskCheckedChanged(task, isChecked)
}
override fun onAttach(context: Context) {
super.onAttach(context)
childFragmentListener = context as ChildFragmentListener
}
override fun onPause() {
super.onPause()
Logger.i(TAG, "onPause", "TasksListFragment paused")
}
override fun onDestroyView() {
super.onDestroyView()
searchView.setOnQueryTextListener(null)
}
}
TodayFragment.kt
#ExperimentalCoroutinesApi
#AndroidEntryPoint
class TodayFragment : Fragment(R.layout.fragment_parent_today), TasksListFragment.ChildFragmentListener {
private val viewModel: TodayViewModel by viewModels()
private lateinit var binding: FragmentParentTodayBinding
private var fabClicked: Boolean = false
private lateinit var tasksListMenu: Menu
private lateinit var viewPager: ViewPager2
private val rotateOpen: Animation by lazy { AnimationUtils.loadAnimation(requireContext(), R.anim.rotate_open_anim) }
private val rotateClose: Animation by lazy { AnimationUtils.loadAnimation(requireContext(), R.anim.rotate_close_anim) }
private val fromBottom: Animation by lazy { AnimationUtils.loadAnimation(requireContext(), R.anim.from_bottom_anim) }
private val toBottom: Animation by lazy { AnimationUtils.loadAnimation(requireContext(), R.anim.to_bottom_anim) }
private val fadeIn: Animation by lazy { AnimationUtils.loadAnimation(requireContext(), R.anim.fade_in) }
private val fadeOut: Animation by lazy { AnimationUtils.loadAnimation(requireContext(), R.anim.fade_out) }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = FragmentParentTodayBinding.bind(view)
binding.apply {
tasksListTransparentWhiteScreen.setOnClickListener {
fabAnimationsRollBack(binding)
fabClicked = !fabClicked
}
}
setChildFragmentMenus()
initViewPagerWithTabLayout(binding)
todayDateDisplay(binding)
initFabs(binding)
loadTodayEventCollector()
getFragmentResultListeners()
}
private fun setChildFragmentMenus(){
val tasksListFragment = TasksListFragment()
tasksListFragment.setListener(this)
Logger.i(TAG, "setChildFragmentMenus", "TasksListFragment menu set")
}
private fun getFragmentResultListeners() {
setFragmentResultListener("add_edit_request"){_, bundle ->
val result = bundle.getInt("add_edit_result")
onFragmentResult(result)
}
setFragmentResultListener("create_set_request_2"){_, bundle ->
val result = bundle.getInt("create_set_result_2")
onFragmentResult(result)
}
setFragmentResultListener("task_added_to_set_request"){_, bundle ->
val result = bundle.getInt("task_added_to_set_result")
val message = bundle.getString("task_added_to_set_message")
onFragmentResult(result, message)
}
setFragmentResultListener("task_added_from_set_request"){_, bundle ->
val result = bundle.getInt("task_added_from_set_result")
val message = bundle.getString("task_added_from_set_message")
onFragmentResult(result, message)
}
}
private fun onFragmentResult(result: Int, message: String? = ""){
viewModel.onFragmentResult(result, message)
}
/**
* TodayViewModel.TodayEvent.NavigateToAddTaskScreen: Relevant to this class. Belongs to Fab which are all in this class.
* TodayViewModel.TodayEvent.ShowTaskSavedConfirmationMessage: Relevant to this class. Belongs to onFragmentResultListener which is here.
* TodayViewModel.TodayEvent.ShowTaskSavedInNewOrOldSetConfirmationMessage: Relevant to this class. Belongs to onFragmentResultListener which is here.
* TodayViewModel.TodayEvent.ShowTaskAddedFromSetConfirmationMessage: Relevant to this class. Belongs to onFragmentResultListener which is here.
* TodayViewModel.TodayEvent.NavigateToAddTasksFromSetBottomSheet: Relevant to this class. Belongs to Fab which are all in this class.
*/
private fun loadTodayEventCollector() {
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
viewModel.todayEvent.collect { event ->
when (event) {
is TodayViewModel.TodayEvent.NavigateToAddTaskScreen -> {
val action = TodayFragmentDirections
.actionTodayFragmentToTaskAddEditFragment(task = null, title = "Add task"
, taskinset = null, origin = 1)
findNavController().navigate(action)
}
is TodayViewModel.TodayEvent.ShowTaskSavedConfirmationMessage -> {
Snackbar.make(requireView(), event.msg, Snackbar.LENGTH_LONG).show()
setViewPagerPage(0)
}
is TodayViewModel.TodayEvent.ShowTaskSavedInNewOrOldSetConfirmationMessage -> {
Snackbar.make(requireView(), event.msg.toString(), Snackbar.LENGTH_LONG).show()
}
is TodayViewModel.TodayEvent.ShowTaskAddedFromSetConfirmationMessage -> {
Snackbar.make(requireView(), event.msg.toString(), Snackbar.LENGTH_LONG).show()
fabClicked = true
setFabAnimationsAndViewStates(binding)
setViewPagerPage(0)
}
is TodayViewModel.TodayEvent.NavigateToAddTasksFromSetBottomSheet -> {
val action = TasksListFragmentDirections
.actionGlobalSetBottomSheetDialogFragment(task = null, origin = 2)
findNavController().navigate(action)
}
}.exhaustive
}
}
}
// This will soon be used to be 1
private fun setViewPagerPage(index: Int){
viewModel.postActionWithDelay(300, object: TodayViewModel.PostActionListener{
override fun onDelayFinished() {
viewPager.setCurrentItem(index, true)
}
})
}
private fun todayDateDisplay(binding: FragmentParentTodayBinding) {
binding.apply {
tasksListDateheader.apply {
dateHeaderDayofmonth.text = viewModel.getCurrentDayOfMonth()
dateHeaderMonth.text = viewModel.getCurrentMonth()
dateHeaderYear.text = viewModel.getCurrentYear()
dateHeaderDayofweek.text = viewModel.getCurrentDayOfWeek()
}
}
}
private fun initViewPagerWithTabLayout(binding: FragmentParentTodayBinding) {
viewPager = binding.todayViewpager
val tabLayout: TabLayout = binding.todayTablayout
viewPager.adapter = activity?.let { TodayPagerAdapter(it) }
Logger.i(TAG, "initViewPagerWithTabLayout", "viewPager is not null")
TabLayoutMediator(tabLayout, viewPager) { tab, index ->
tab.text = when (index) {
0 -> "Tasks"
1 -> "Journal"
else -> throw Resources.NotFoundException("Tab not found at position")
}.exhaustive
when (index) {
0 -> {
}
1 -> {
fabClicked = false
}
}
}.attach()
}
private fun initFabs(binding: FragmentParentTodayBinding) {
binding.apply {
tasksListFab.setOnClickListener {
onMainFabClick(binding)
}
tasksListSubFab1.setOnClickListener {
Logger.i(TAG, "initFabs", "Coming soon")
}
tasksListSubFab2.setOnClickListener {
viewModel.onAddTasksFromSetClick()
}
tasksListSubFab3.setOnClickListener {
viewModel.onAddNewTaskClick()
}
}
}
private fun onMainFabClick(binding: FragmentParentTodayBinding) {
setFabAnimationsAndViewStates(binding)
}
private fun setFabAnimationsAndViewStates(binding: FragmentParentTodayBinding) {
setFabAnimationVisibilityAndClickability(binding, fabClicked)
fabClicked = !fabClicked
}
private fun setFabAnimationVisibilityAndClickability(binding: FragmentParentTodayBinding, clicked: Boolean) {
if (!clicked) fabAnimationsRollIn(binding) else fabAnimationsRollBack(binding)
}
private fun fabAnimationsRollIn(binding: FragmentParentTodayBinding) {
binding.apply {
HGDAAnimationUtils.apply {
HGDAViewStateUtils.apply {
setViewAnimation(v1 = tasksListFab, a = rotateOpen)
setViewAnimation(v1 = tasksListSubFab1, v2 = tasksListSubFab2, v3 = tasksListSubFab3, a = fromBottom)
setViewAnimation(v1 = tasksListSubFab1Tv, v2 = tasksListSubFab2Tv, v3 = tasksListSubFab3Tv, a = fromBottom)
setViewAnimation(v1 = tasksListTransparentWhiteScreen, a = fadeIn)
setViewVisibility(v1 = tasksListSubFab1, v2 = tasksListSubFab2, v3 = tasksListSubFab3
, v4 = tasksListSubFab1Tv, v5 = tasksListSubFab2Tv, v6 = tasksListSubFab3Tv, visibility = View.VISIBLE)
setViewVisibility(v1 = tasksListTransparentWhiteScreen, visibility = View.VISIBLE)
setViewClickState(v1 = tasksListSubFab1, v2 = tasksListSubFab2, v3 = tasksListSubFab3, clickable = true)
setViewClickState(v1 = tasksListTransparentWhiteScreen, clickable = true)
}
}
}
}
private fun fabAnimationsRollBack(binding: FragmentParentTodayBinding) {
binding.apply {
HGDAAnimationUtils.apply {
HGDAViewStateUtils.apply {
setViewAnimation(v1 = tasksListFab, a = rotateClose)
setViewAnimation(v1 = tasksListSubFab1, v2 = tasksListSubFab2, v3 = tasksListSubFab3, a = toBottom)
setViewAnimation(v1 = tasksListSubFab1Tv, v2 = tasksListSubFab2Tv, v3 = tasksListSubFab3Tv, a = toBottom)
setViewAnimation(v1 = tasksListTransparentWhiteScreen, a = fadeOut)
setViewVisibility(v1 = tasksListSubFab1, v2 = tasksListSubFab2, v3 = tasksListSubFab3
, v4 = tasksListSubFab1Tv, v5 = tasksListSubFab2Tv, v6 = tasksListSubFab3Tv, visibility = View.INVISIBLE)
setViewVisibility(v1 = tasksListTransparentWhiteScreen, visibility = View.INVISIBLE)
setViewClickState(v1 = tasksListSubFab1, v2 = tasksListSubFab2, v3 = tasksListSubFab3, clickable = false)
setViewClickState(v1 = tasksListTransparentWhiteScreen, clickable = false)
}
}
}
}
override fun onFragmentChanged(menu: Menu) {
tasksListMenu = menu
}
override fun onPause() {
super.onPause()
tasksListMenu.clear()
}
}
Fragment is not a Context i.e fragment is not a child of context .
So when you try to cast context as ChildFragmentListener you are actually casting your Activity to ChildFragmentListener which is giving you this RuntimeException . to make it work you can use childFragmentListener = parentFragment as ChildFragmentListener
Also if your Doing this you do not need setListener method anymore.
On other hand i would suggest you do not use listeners to communicate b/w fragments . I see you already using viewModel so just use a shared one to communicate . You can get a shared ViewModel inside child by creating it with parentFragment.

Alert Dialog positive button issue

I am developing an app that asks the user to type task's name, description....,but when the user intends to leave the edit page without saving the task I need to pop up a dialog to confirm leaving, so if he clicked abandon changes he must return to the home page of the app otherwise he will stay at the same page.
I have problem with this, when the user clicks abandon changes the dialog dismisses and he stays at the edit page.
MainActivity.kt
package muhabalafandi.example.tasktimer
import android.content.res.Configuration
import android.os.Bundle
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.content_main.*
private const val TAG = "MainActivity"
private const val DIALOG_ID_CANCEL_EDIT = 1
class MainActivity : AppCompatActivity(), AddEditFragment.OnSaveClicked,
MainActivityFragment.OnTaskEdit,
AppDialog.DialogEvents {
// Whether or the activity is in 2-pane mode
// i.e. running in landscape, or on a tablet.
private var mTwoPane = false
override fun onCreate(savedInstanceState: Bundle?) {
Log.d(TAG, "onCreate: starts")
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
mTwoPane = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
Log.d(TAG, "onCreate: twoPane is $mTwoPane")
val fragment = findFragmentById(R.id.task_details_container)
if (fragment != null) {
// There was an existing fragment to edit a task, make sure the panes are set correctly
showEditPane()
} else {
task_details_container.visibility = if (mTwoPane) View.INVISIBLE else View.GONE
mainFragment.visibility = View.VISIBLE
}
Log.d(TAG, "onCreate: finished")
}
private fun showEditPane() {
task_details_container.visibility = View.VISIBLE
// hide the left hand pane, if in single pane view
mainFragment.visibility = if (mTwoPane) View.VISIBLE else View.GONE
}
private fun removeEditPane(fragment: Fragment? = null) {
Log.d(TAG, "removeEditPane called")
if (fragment != null) {
// supportFragmentManager.beginTransaction()
// .remove(fragment)
// .commit()
removeFragment(fragment)
}
// Set the visibility of the right hand pane
task_details_container.visibility = if (mTwoPane) View.INVISIBLE else View.GONE
// and show the left hand pane
mainFragment.visibility = View.VISIBLE
supportActionBar?.setDisplayHomeAsUpEnabled(false)
}
override fun onSaveClicked() {
Log.d(TAG, "onSaveClicked: called")
removeEditPane(findFragmentById(R.id.task_details_container))
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.menu_main, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
when (item.itemId) {
R.id.menuMain_addTask -> taskEditRequest(null)
// R.id.menuMain_settings -> true
android.R.id.home -> {
Log.d(TAG, "onOptionsItemSelected: home button pressed")
val fragment = findFragmentById(R.id.task_details_container)
// removeEditPane(fragment)
if ((fragment is AddEditFragment) && fragment.isDirty()) {
showConfirmationDialog(
DIALOG_ID_CANCEL_EDIT,
getString(R.string.cancelEditDialogMessage),
R.string.cancelEditDialogPositiveCaption,
R.string.cancelEditDialogNegativeCaption
)
Log.d(TAG, "dialogId : $DIALOG_ID_CANCEL_EDIT")
} else {
removeEditPane(fragment)
}
}
}
return super.onOptionsItemSelected(item)
}
override fun onTaskEdit(task: Task) {
taskEditRequest(task)
}
private fun taskEditRequest(task: Task?) {
Log.d(TAG, "taskEditRequest: starts")
// Create a new fragment to edit the task
// val newFragment = AddEditFragment.newInstance(task)
// supportFragmentManager.beginTransaction()
// .replace(R.id.task_details_container, newFragment)
// .commit()
replaceFragment(AddEditFragment.newInstance(task), R.id.task_details_container)
showEditPane()
Log.d(TAG, "Exiting taskEditRequest")
}
override fun onBackPressed() {
val fragment = findFragmentById(R.id.task_details_container)
if (fragment == null || mTwoPane) {
super.onBackPressed()
} else {
// removeEditPane(fragment)
if ((fragment is AddEditFragment) && fragment.isDirty()) {
showConfirmationDialog(
DIALOG_ID_CANCEL_EDIT,
getString(R.string.cancelEditDialogMessage),
R.string.cancelEditDialogPositiveCaption,
R.string.cancelEditDialogNegativeCaption
)
} else {
removeEditPane(fragment)
}
}
}
override fun onPositiveDialogResult(dialogId: Int, args: Bundle) {
Log.d(TAG, "onPositiveDialogResult: called with dialogId $dialogId")
if (dialogId == DIALOG_ID_CANCEL_EDIT) {
val fragment = findFragmentById(R.id.task_details_container)
removeEditPane(fragment)
}
}
override fun onStart() {
Log.d(TAG, "onStart: called")
super.onStart()
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
Log.d(TAG, "onRestoreInstanceState: called")
super.onRestoreInstanceState(savedInstanceState)
}
override fun onResume() {
Log.d(TAG, "onResume: called")
super.onResume()
}
override fun onPause() {
Log.d(TAG, "onPause: called")
super.onPause()
}
override fun onSaveInstanceState(outState: Bundle) {
Log.d(TAG, "onSaveInstanceState: called")
super.onSaveInstanceState(outState)
}
override fun onStop() {
Log.d(TAG, "onStop: called")
super.onStop()
}
override fun onDestroy() {
Log.d(TAG, "onDestroy: called")
super.onDestroy()
}
}
AppDialog.kt
package muhabalafandi.example.tasktimer
import android.app.AlertDialog
import android.app.Dialog
import android.content.Context
import android.content.DialogInterface
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.DialogFragment
private const val TAG = "AppDialog"
const val DIALOG_ID = "id"
const val DIALOG_MESSAGE = "message"
const val DIALOG_POSITIVE_RID = "positive_rid"
const val DIALOG_NEGATIVE_RID = "negative_rid"
class AppDialog : DialogFragment() {
private var dialogEvents: DialogEvents? = null
internal interface DialogEvents {
fun onPositiveDialogResult(dialogId: Int, args: Bundle)
// fun onNegativeDialogResult(dialogId: Int, args: Bundle)
// fun onDialogCancelled(dialogId: Int)
}
override fun onAttach(context: Context) {
Log.d(TAG, "onAttach called: context is $context")
super.onAttach(context)
// Activities/Fragments containing this fragment must implement its callbacks.
dialogEvents = try {
// Is there a parent fragment? If so, that will be what we call back
parentFragment as? DialogEvents
} catch (e: TypeCastException) {
try {
// No parent fragment, so call back the Activity instead
context as DialogEvents
} catch (e: ClassCastException) {
// Activity doesn't implement the interface
throw ClassCastException("Activity $context must implement AppDialog.DialogEvents interface")
}
} catch (e: ClassCastException) {
// Parent fragment doesn't implement the interface
throw ClassCastException("Fragment $parentFragment must implement AppDialog.DialogEvents interface")
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
Log.d(TAG, "onCreateDialog called")
val builder = AlertDialog.Builder(requireContext())
// fix "smart cast to Bundle is impossible, because 'arguments' is a mutable property that could have been changed by this time"
val arguments = arguments
val dialogId: Int
val messageString: String?
var positiveStringId: Int
var negativeStringId: Int
if (arguments != null) {
dialogId = arguments.getInt(DIALOG_ID)
messageString = arguments.getString(DIALOG_MESSAGE)
if (dialogId == 0 || messageString == null) {
throw IllegalArgumentException("DIALOG_ID and/or DIALOG_MESSAGE not present in the bundle")
}
positiveStringId = arguments.getInt(DIALOG_POSITIVE_RID)
if (positiveStringId == 0) {
positiveStringId = R.string.ok
}
negativeStringId = arguments.getInt(DIALOG_NEGATIVE_RID)
if (negativeStringId == 0) {
negativeStringId = R.string.cancel
}
} else {
throw IllegalArgumentException("Must pass DIALOG_ID and DIALOG_MESSAGE in the bundle")
}
return builder.setMessage(messageString).setPositiveButton(positiveStringId) { _, _ ->
// callback positive result function
dialogEvents?.onPositiveDialogResult(dialogId, arguments)
}.setNegativeButton(negativeStringId) { _, _ ->
// callback negative result function, if you want to implement it.
// dialogEvents?.onNegativeDialogResult(dialogId, arguments)
}.create()
}
override fun onDetach() {
Log.d(TAG, "onDetach called")
super.onDetach()
// Reset the active callbacks interface, because we're no longer attached.
dialogEvents = null
}
override fun onCancel(dialog: DialogInterface) {
Log.d(TAG, "onCancel called")
val dialogId = requireArguments().getInt(DIALOG_ID)
// dialogEvents?.onDialogCancelled(dialogId)
}
}
buttonYes.setOnClickListener {
findNavController().popBackStack()
findNavController().popBackStack()
{

kotlin.UninitializedPropertyAccessException: lateinit property postFragment has not been initialized on FragmentStateAdapter Dagger Hilt

i tried to implement viewpager2 with tabLayout with dagger-hilt. then i got error when implementing viewpager2 on dagger hilt "kotlin.UninitializedPropertyAccessException: lateinit property postFragment has not been initialized". Here's my code
ProfilePagerAdapter
class ProfilesPagerAdapter(activity: AppCompatActivity): FragmentStateAdapter(activity) {
private lateinit var postFragment: PostFragment
override fun getItemCount(): Int = 3
override fun createFragment(position: Int): Fragment {
val fragment: Fragment? = null
when (position) {
0 -> postFragment
1 -> postFragment
2 -> postFragment
}
return fragment as Fragment
}
}
ProfileActivity
#AndroidEntryPoint
class ProfileActivity : AppCompatActivity() {
companion object {
#StringRes
private val TAB_TITLES = intArrayOf(
R.string.tab_text_post,
R.string.tab_text_review,
R.string.tab_text_koleksi
)
}
private lateinit var binding: ActivityProfileBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityProfileBinding.inflate(layoutInflater)
setContentView(binding.root)
supportActionBar?.hide()
val profilesPagerAdapter = ProfilesPagerAdapter(this)
binding.apply {
viewpagerProfile.adapter = profilesPagerAdapter
TabLayoutMediator(tabProfile, viewpagerProfile) { tab, position ->
tab.text = resources.getString(TAB_TITLES[position])
}.attach()
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.share -> {
Toast.makeText(this, "Fitur belum dibuat", Toast.LENGTH_SHORT).show()
return true
}
R.id.help -> {
Toast.makeText(this, "Fitur belum dibuat", Toast.LENGTH_SHORT).show()
return true
}
R.id.home_menu -> {
Toast.makeText(this, "Fitur belum dibuat", Toast.LENGTH_SHORT).show()
return true
}
else -> return true
}
}
}
A tutorial or reference about implementing viewpager2 using dagger-hilt is appreciated

how to get callback between fragments in kotlin

I am trying to get a callback from one child fragment to parent fragment in kotlin and then later call a function in parent fragment to manage other layouts inside the child fragment.
I know how to get callback from a fragment to the activity and access the function in the fragment in the callback we cannot do that in Kotlin because of companion objects.
Another way is to pass context of fragment to child fragment and implement the interface using fragment's content in onAttach() but I cannot pass the context of parent fragment via constructor as I am using factory static methods.
Any help will be highly appreciated.
Child Fragment : DriverStatusFragment.kt
companion object {
#JvmStatic
fun newInstance() =
DriverStatusFragment().apply {
arguments = Bundle().apply {
}
}
}
override fun onAttach(context: Context) {
super.onAttach(context)
if (context is OnCurrentOrderStatusChanged) {
orderStatusChangedCallBack = context
}
}
private fun handleUserOnlineStatus(isOnline: Boolean) {
CustomPreferences.setBooleanPreference(context!!, PrefEntity.IS_ONLINE, isOnline)
when (isOnline) {
true -> {
binding.idOnlineStatus.text = getString(R.string.online)
binding.idOnlineStatusImage.setImageResource(R.drawable.circle_green)
//testing:
orderStatusChangedCallBack?.orderStatusChanged(CONSTANTS.NEW_ORDER)
}
false -> {
binding.idOnlineStatus.text = getString(R.string.offline)
binding.idOnlineStatusImage.setImageResource(R.drawable.circle_gray)
}
}
}
Parent Fragment : HomeFragment.kt
class HomeFragment : Fragment(), OnMapReadyCallback, OnCurrentOrderStatusChanged {
companion object {
#JvmStatic
fun newInstance() =
HomeFragment().apply {
arguments = Bundle().apply {
}
}
}
fun handleOrderStatus(status: String) {
when (status) {
CONSTANTS.IDLE -> {
replaceFragment(
DriverStatusFragment.newInstance(),
DriverStatusFragment::class.java.simpleName
)
}
CONSTANTS.NEW_ORDER -> {
replaceFragment(
NewOrderFragment.newInstance(false),
NewOrderFragment::class.java.simpleName
)
}
CONSTANTS.ORDER_ACCEPTED -> {
replaceFragment(
EnRouteFragment.newInstance(CONSTANTS.ORDER_ACCEPTED),
EnRouteFragment::class.java.simpleName
)
}
CONSTANTS.ARRIVED_AT_DROP_LOCATION -> {
replaceFragment(
OrderDeliveredFragment.newInstance(CONSTANTS.ARRIVED_AT_DROP_LOCATION),
OrderDeliveredFragment::class.java.simpleName
)
}
CONSTANTS.DELIVERED -> {
replaceFragment(
DriverStatusFragment.newInstance(),
DriverStatusFragment::class.java.simpleName
)
}
}
override fun onAttach(context: Context) {
super.onAttach(context)
this.context = context
}
**NOT RECEIVING CALLBACK HERE**
override fun orderStatusChanged(orderStatus: String) {
CommonUtils.showToast(context!!, "REAched here")
handleOrderStatus(orderStatus)
}
}

How to change fragment inside getItem of FragmentStatePagerAdapter

I have this code, where I want the first fragment of my FragmentStatePageAdapter(inside a viewpager), to change, depending on some boolean values.
inner class MainFragmentAdapter(fragmentManager: FragmentManager) :
FragmentStatePagerAdapter(fragmentManager) {
override fun getCount(): Int {
return MainView.Fragments.values().size
}
override fun getItem(position: Int): Fragment? {
return when (position) {
0 -> {
if (booleanOne && !booleanTwo) {
FragmentOne.newInstance()
} else if (booleanTwo && !booleanOne) {
FragmentTwo.newInstance()
} else {
FragmentThree.newInstance()
}
}
else -> null
}
}
}
As of now, the only solution I found, was when I personally knew one of the boolean values changed, I just did something similar to this :
_fragmentAdapter = MainFragmentAdapter(getLifecycleFragmentManager())
fragmentViewPager.adapter = _fragmentAdapter
So I just set the adapter once again, but it seems rather off to me to do this. Can't I force it to just calculate getItem once again?
Inside your class make these changes, notifyDataSetChanged() and then override the getItemPosition() which will be called on every notify and then just return the respective constant, POSITION_NONE if you want to see new changes or else POSITION_UNCHANGED
inner class MainFragmentAdapter(fragmentManager: FragmentManager) :
FragmentStatePagerAdapter(fragmentManager) {
override fun getCount(): Int {
return MainView.Fragments.values().size
}
override fun getItem(position: Int): Fragment? {
return when (position) {
0 -> {
if (booleanOne && !booleanTwo) {
FragmentOne.newInstance()
} else if (booleanTwo && !booleanOne) {
FragmentTwo.newInstance()
} else {
FragmentThree.newInstance()
}
notifyDataSetChanged();
}
else -> null
}
}
override fun getItemPosition(#NonNull `object`: Any): Int {
// this method will be called for every fragment in the ViewPager
return if (`object` is FragmentOne) {
POSITION_UNCHANGED // don't force a reload
} else {
// POSITION_NONE means something like: this fragment is no longer valid
// triggering the ViewPager to re-build the instance of this fragment.
POSITION_NONE
}
}
}

Categories

Resources