I want to show a list of Strings in a MaterialAlertDialog. I'm loading them from Room Db within a suspended function and use a callback to set the items in the DialogFragment. Here the code:
PlayerDao.kt
@Dao
interface PlayerDao {
@Query("SELECT * FROM players WHERE playable = 1")
suspend fun getPlayableCharacters():List<PlayerEntity>
}
PlayerRepository.kt
@Singleton
class PlayerRepository @Inject constructor(private val db: AppDatabase) {
suspend fun getPlayableCharacters():List<Player>{
return db.playerDao().getPlayableCharacters().map {
Player.fromEntity(it)
}
}
}
WelcomeViewModel.kt
@HiltViewModel
class WelcomeViewModel @Inject constructor(private val repository: PlayerRepository) : ViewModel() {
fun loadPlayableChars(onResult: (List<Pair<String, Int>>) -> Unit) {
viewModelScope.launch {
val result: List<Pair<String, Int>> = withContext(Dispatchers.IO) {
repository.getPlayableCharacters().map {
Pair(it.name, it.getLevel())
}
}
onResult(result)
}
}
}
WelcomeFragment.kt
@AndroidEntryPoint
class WelcomeFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentWelcomeBinding.inflate(inflater, container, false)
binding.loadCharButton.setOnClickListener {
SelectCharacterDialogFragment().show(childFragmentManager, "SelectCharacterDialogFragment")
}
return binding.root
}
}
SelectCharacterDialogFragment.kt
@AndroidEntryPoint
class SelectCharacterDialogFragment : DialogFragment() {
val viewModel: WelcomeViewModel by viewModels()
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val itemsToShow = arrayOf<String>()
val items = mutableListOf<Pair<String, Int>>()
val dialogBuilder = MaterialAlertDialogBuilder(requireContext())
.setTitle("Choose a character")
viewModel.loadPlayableChars { list ->
Log.d("DIALOG", "in Callback: $list")
items = list
val itemsToShow = items.map {
"${it.first}: ${it.second}"
}.toTypedArray()
dialogBuilder.setItems(itemsToShow) { dialog, which ->
when (which) {
0 -> Log.d("Dialog", "$which")
else -> Log.d("Dialog", "$which")
}
}
}
Log.d("DIALOG", "Returning dialog")
return dialogBuilder.create()
}
}
The log output inside the callback tells me that the data is available. But to late. The method has already returned when the callback is executed:
D/DIALOG: Returning dialog
I/System.out: SQL: SELECT * FROM players WHERE playable = 1
Here is a screenshot:
What did I wrong? What is the best practice to make a one-shot request to show items inside an AlertDialog?
CodePudding user response:
This behaviour occurs because your code inside onCreateDialog
does not wait for the result of viewModel.loadPlayableChars
(which operates async) but continue and returns the created dialog.
As you should not wait for the result of viewModel.loadPlayableChars
on the MainThread
and then return the Dialog
, I would load the data and open the Dialog
within the callback passing the loaded data to the dialog.