Creating Reusable Dialog In MVVM | Part 1 : Sharing Data | Android

Reuse Code, Reduce Bug

Prem Thakur
6 min readOct 10, 2023

In this article, we will create a dialog that will be used on more than one screen (screen independent). The dialog will have these features:

  • The dialog will need some data that will be shown in the dialog.
  • It will contain a button, the click event of which will be handled by the parent (activity or fragment) that created it.
  • The dialog should also survive configuration changes and the data should not lost.

This type of dialog is common in filters when you are showing a list of items (data) in the dialog and you want the user to choose an item (click event). Also, you want the design of the dialog to be consistent throughout the app.

Here we will be using the shared ViewModel technique but in a more abstract form so that the dialog will be independent of the parent (Fragment or Activity). For this tutorial, we will be using BottomSheetDialogFragment as the dialog. You could also use DialogFragment as per your requirement.

Project Setup

I have created a starter android project for the this tutorial. You can find it in the below repository.

After cloning, switch to project_setup branch. Here is an overview of the project:

  • data : This package contains AppRepository which has two lists (authors and books).
  • ui : It contains UI related classes like activity and the dialog. This app has two activities (BooksActivity and AuthorsActivity) which lets the user choose a Book and Author respectively from their list. It uses ListBottomSheet Dialog to display the list. ListBottomSheet has TextView (for the title) and a RecyclerView to show a list using ListBottomSheetAdapter. The adapter takes a list of String and a function (that will be called, when an item is clicked, with the item position) in the constructor.

Both Activity has a Choose button which shows ListBottomSheet. Initially, when you click on the choose button, the sheet will show nothing in the RecyclerView. Here is what the project_startup branch looks like:

BooksActivity and AuthorsActivity

High-Level Design

Below is the HLD of the Reusable Dialog that we are going to implement.

Sharing List and Title

  1. Add the following dependencies in build.gradle (app module).
dependencies {
def activity_version = "1.7.2"
def fragment_version = "1.6.1"

implementation "androidx.activity:activity-ktx:$activity_version"
implementation "androidx.fragment:fragment-ktx:$fragment_version"

}

2. Create an interface ListBottomSheetViewModel. It will be used by the bottom sheet as a data source.

interface ListBottomSheetViewModel {
//we are just showing String in the RecyclerView
//You can use a data class according to the list item UI.
//For eg. data class ListItem(img : Uri?, title : String) if you are showing
//title with an optional image.
val list : List<String>
}

3. Make ListBottomSheet an abstract class and modify it to use ViewModel as a data source for complex types (here it is a list of String)

//Make it abstract, we will make a subclass where we will define how to get these args
//also, it tells we should not create an instance of ListBottomSheet directly
abstract class ListBottomSheet(
title : String,
lazyViewModel : Fragment.() -> Lazy<ListBottomSheetViewModel>
) : BottomSheetDialogFragment() {

init {
//title is not a complex type, so we can store it in the arguments
arguments = Bundle().apply {
putString(TITLE_KEY, title)
}
}

//using Kotlin delegate property
private val viewModel by lazyViewModel()
private val adapter by lazy {
//populate adapter with viewmodel list
ListBottomSheetAdapter(viewModel.list){}
}

//get title from saved args
private val title
get() = arguments?.getString(TITLE_KEY) ?: ""

...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) = with(binding) {
//adapter is first used here
//so it will be initialize here and thus viewModel
rvList.adapter = adapter
//set the title
dialogTitle.text = title
}
}

private const val TITLE_KEY = "list_bottom_sheet_title"

Let’s understand lazyViewModel : Fragment.() -> Lazy<ListBottomSheetViewModel>.

lazyViewModel is an extension function type with a receiver type Fragment that returns a Lazy<ListBottomSheetViewModel>. In simple terms, we are saying that lazyViewModel function will be called inside a Fragment. Thus we can call any Fragment function inside that lambda.

It returns Lazy<ListBottomSheetViewModel> so that ViewModel will be initialized when we first use it. Here, we are using viewModel in the lazy block of the adapter so that viewModel and adapter will be created when the adapter is first accessed (in onViewCreated).

4. Create ViewModels for both activities and implements ListBottomSheetViewModel on both of them.

class BooksViewModel : ViewModel(), ListBottomSheetViewModel{
private val bookList = AppRepository.books

override val list: List<String> = bookList.map {
//map the list to bottom sheet list type
"${it.name} by ${it.author}"
}

}
class AuthorsViewModel : ViewModel(), ListBottomSheetViewModel{

private val authorList = AppRepository.authors

override val list: List<String> = authorList.map {
it.name
}
}

5. Create a subclass of ListBottomSheet and show it instead. The sub-class will define how to create ListBottomSheetViewModel.

class BooksActivity : AppCompatActivity() {
//we will use BookListBottomSheet (subclass of ListBottomSheet)
private val listBottomSheet = BookListBottomSheet()
private val viewModel : BooksViewModel by viewModels()

override fun onCreate(savedInstanceState: Bundle?) {
...
findViewById<Button>(R.id.choose_btn).setOnClickListener {

//if the parent is fragment : use childFragmentManager
listBottomSheet.show(supportFragmentManager,"Books List")
}
}

//Creae a subclass of ListBottomSheet
class BookListBottomSheet: ListBottomSheet(
title = "Choose Book",

//if the parent is a fragment : use requireParentFragment()
lazyViewModel = {
//here this is Fragment
this.viewModels<BooksViewModel>({ ownerProducer = requireActivity() })
}
)
}

Similarly in AuthorsActivity

class AuthorsActivity : AppCompatActivity() {

private val listBottomSheet = AuthorListBottomSheet()
private val viewModel : AuthorsViewModel by viewModels()

override fun onCreate(savedInstanceState: Bundle?) {
findViewById<Button>(R.id.choose_btn).setOnClickListener {
//if the parent is fragment : use childFragmentManager
listBottomSheet.show(supportFragmentManager,"Authors List")
}
}

class AuthorListBottomSheet: ListBottomSheet(
title = "Choose Author",
//if the parent is a fragment : use requireParentFragment()
lazyViewModel = { viewModels<AuthorsViewModel>({ requireActivity() }) }
)
}

viewModels is an extension function to the Fragment in Android that returns a Lazy ViewModel. It has a parameter ownerProducer where we tell it to retrieve the ViewModel using the parent activity’s scope.

If you are showing ListBottomSheet in a Fragment (parent) instead, just replace supportFragmentManager with childFragmentManager and requireActivity() with requireParentFragment() .

Learn more about it here.

We are not creating anonymous object of ListBottomSheet because to recreate it on configuration change, FragmentManager needs public static subclass of Fragment and thus will throw an Exception when we call listBottomSheet.show.

That’s it now we can see a list of Books and Authors in the bottom sheet when we click on the respective activities choose button.

Summary

That’s it, now we can use our bottom sheet anywhere in the app where we want to show a list of items. You can customize it further to support a Flow<Response> to handle Loading, Success, and Error state. The subclassing also enables us to modify some of the UI elements (like textSize of TextView title). Just we have to create an open valor abstract fun for that and the subclass will give the implementation.

You can find the code of this tutorial in the share_list branch in the below GitHub repository.

In the next part, we will discuss how to handle click events and a crash that happens when dismissing dialog after rotation.

Thank you for joining me on this journey of Android development. For more insights, tips, and in-depth articles, feel free to connect with me on LinkedIn and Medium. If you have any questions or would like to share your thoughts, please don’t hesitate to leave a comment below. Your engagement and curiosity are greatly appreciated. Until next time, happy coding!

--

--