Is it possible to invoke DialogFragment from custom view? - android

I've tried to invoke DialogFragment from custom view:
DetailsDialogFragment
.newInstance(newSelectedDate, adapterItems[newPosition].progress)
.apply {
show(childFragmentManager, SCORE_DETAILS_DIALOG_TAG)
}
where DetailsDialogFragment looks like this:
class DetailsDialogFragment : AppCompatDialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return requireActivity().let {
val dialog = Dialog(requireContext(), R.style.CustomDialog)
dialog.window?.setDimAmount(BaseDialogFragment.SCRIM_OPACITY)
dialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
dialog
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_details_dialog, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.rootView.apply {
findViewById<TextView>(R.id.titleTextView).text = arguments?.getString(ARGS_MONTH)
findViewById<ActionButton>(R.id.button).setOnClickListener {
dismiss()
}
findViewById<ImageView>(R.id.closeImageView).setOnClickListener {
dismiss()
}
}
}
companion object {
fun newInstance(
month: String,
score: Int
): DetailsDialogFragment {
return DetailsDialogFragment()
.apply {
arguments = bundleOf(
ARGS_MONTH to month,
ARGS_SCORE to score
)
}
}
}
}
But I receive the following error:
IllegalStateException: Fragment DetailsDialogFragment has not been attached yet.
at androidx.fragment.app.Fragment.getChildFragmentManager(Fragment.java:980)
...
Is it possible to invoke DialogFragment from custom view at all?

The reason of this exception is that you're trying to use the childFragmentManager of your freshly newly created instance, which is of course not possible since the Dialog fragment hasn't yet has its internals initialized yet (including its childFragmentManager).
If you're using AndroidX I'd use the findFragment extension method inside your custom view and try to do:
Inside your custom view
val dialogFragment = DetailsDialogFragment
.newInstance(newSelectedDate, adapterItems[newPosition].progress)
dialogFragment.show(findFragment().childFragmentManager, SCORE_DETAILS_DIALOG_TAG)

Related

Updating View of AlertDialog from Fragment

I am not sure why I cannot update the imageView of a custom dialog layout xml which is opened from inside a fragment.
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setView(R.layout.qr_code_layout_dialog);
imageFromLayout.setImageBitmap(myBitmap);
builder.show();
I have also tried to use a Dialog object (not AlertDialog) but it will not even open, therefore AlertDialog opens but will not show any images. Nor will it allow me to update the images from within the java code.
How can I update the imageView image of the layout dialog from within the fragment?
You can try make realisation of it
abstract class ObservableDialog<T>(private val callBack: (T) -> Unit): DialogFragment(){
companion object{
const val TAG = "MY_TAG"
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return super.onCreateView(inflater, container, savedInstanceState)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setLayoutParam()
}
fun doMyChoice(obj: T){
callBack(obj)
dismiss()
}
private fun setLayoutParam(){
dialog?.window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
}
}
Call it like
val dialog = ObservableDialog<String>{
//do something with result
}
dialog.show(supportFragmentManager, ObservableDialog.TAG)

View Binding in DialogFragment with custom layout in Kotlin

I loved using Kotlin synthetic for its simplicity and code elegance but now they made it depricated and push you to use those ugly view bindings.
There are plenty of answers on how to use it in activites and Fragments, but could not find any examples for custom layout alert dialogs.
Here is the code which worked perfectly with Kontlin synthetic.
import kotlinx.android.synthetic.main.dialog_reward.*
class RewardDialog: DialogFragment() {
private var mView: View? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return mView
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
mView = it.layoutInflater.inflate(R.layout.dialog_reward, null)
AlertDialog.Builder(it).apply {
setView(mView)
}.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//reference layout elements by name freely
}
override fun onDestroyView() {
super.onDestroyView()
mView = null
}
}
How do I migrate this to view bindings?
You can simply use generated ViewBinding views here and not use onCreateDialog
#AndroidEntryPoint
class RewardDialog : DialogFragment() {
private var binding: DialogRewardBinding by autoCleared()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NORMAL, R.style.Theme_MaterialComponents_Light_Dialog_MinWidth)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = DialogRewardBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//reference layout elements by name freely
binding.tvReward.setOnClickListener { }
}
}
autoCleared() is an extension function which will null out all the view in onDestroy() taken from google's architecture component sample here
You can set R.style.Theme_MaterialComponents_Light_Dialog_MinWidth theme in onCreate() so that DialogFragment follows the minWidth defined in Material Compnent theme on deferent screen sizes just like AlertDialog
Edit:
If you are not using the material component library then you can set the width in onViewCreated() using the Kotlin extension.
setWidthPercent(ResourcesCompat.getFloat(resources, R.dimen.dialogWidthPercent).toInt())
Kotlin extenstion function
fun DialogFragment.setWidthPercent(percentage: Int) {
val percent = percentage.toFloat() / 100
val displayMetrics = Resources.getSystem().displayMetrics
val rect = displayMetrics.run { Rect(0, 0, widthPixels, heightPixels) }
val percentWidth = rect.width() * percent
dialog?.window?.setLayout(percentWidth.toInt(), ViewGroup.LayoutParams.WRAP_CONTENT)
}
I have ended up with the following solution. Thanks to #Kishan Maurya for the hint about binding.root.
private var _binding: DialogRewardBinding? = null
private val binding get() = _binding!!
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.run {
//initiate the binding here and pass the root to the dialog view
_binding = DialogRewardBinding.inflate(layoutInflater).apply {
//reference layout elements by name freely here
}
AlertDialog.Builder(this).apply {
setView(binding.root)
}.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
class RewardDialog : DialogFragment() {
private var mView: View? = null
private lateinit var dialogBinding: DialogRewardBinding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// either this way we can init dialogBinding
dialogBinding = DialogRewardBinding.inflate(inflater, container, false)
return dialogBinding.root
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
// either this way we can init dialogBinding
dialogBinding = DataBindingUtil.setContentView(it, R.layout.dialog_reward)
AlertDialog.Builder(it).apply { setView(mView) }.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
with(view) {
myText.text = "Demo"
}
}
override fun onDestroyView() {
super.onDestroyView()
mView = null
}
}
instead of mView, you can use dialogBinding.root
layout
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/myText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="demo"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Android Navigation Component saveState and restoreState

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"

issue with fragment-to-fragment communication using viewModel

I'm new to Kotlin and android development but i can't find why my program isn't working.
I'm trying to be able to communicate from my first fragment to his child, and testing it with a string but it won't display.
Thanks in advance for your help !!!
My first fragment :
class FirstFragment : Fragment() {
private lateinit var viewModel : Communicator
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?) {
super.onViewCreated(view, savedInstanceState)
viewModel = activity?.run {
ViewModelProvider(this).get(Communicator::class.java) // .of supprimé
} ?: throw Exception("Invalid Activity")
viewModel.message.value = "test"
view.findViewById<Button>(R.id.button_stall_selection).setOnClickListener {
findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment)
}
}
}
Here is my second :
class SecondFragment() : Fragment() {
private lateinit var viewModel :Communicator
private var msg: String? = ""
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?) {
super.onViewCreated(view, savedInstanceState)
viewModel = activity?.run {
ViewModelProvider(this).get(Communicator::class.java) // .of deleted
} ?: throw Exception("Invalid Activity")
viewModel.message.observe(viewLifecycleOwner, Observer {
msg = viewModel.message.value
})
view.findViewById<TextView>(R.id.textView_1).text = msg
view.findViewById<Button>(R.id.button_second).setOnClickListener {
findNavController().navigate(R.id.action_SecondFragment_to_FirstFragment)
}
}
}
and finally here is the viewModel class i'm trying to use in order to communicate :
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class Communicator() : ViewModel(){
val message =MutableLiveData<String>()
fun setMsgCommunicator(msg:String){
message.setValue(msg)
}
}
In your FirstFragment, try to call viewModel.setMsgCommunicator("test") instead of directly calling viewModel.message.value = "test"

How to use setUserVisibleHint for fragment in Kotlin

In my application i want use fragment and i want show Toast message just when users see this fragment show message.
I write below codes but when show fragment not show me any Toast!
In java i haven't any issue and show Toast, but when use kotlin not show Toast!
My codes:
class TestFragment : Fragment() {
private val title by lazy { arguments?.getString("title") ?: "" }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_test, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
testFragText.text = title
}
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
if (isVisibleToUser){Toast.makeText(context, "Show", Toast.LENGTH_SHORT).show()}
}
}
How can i fix it?

Categories

Resources