I'm trying to clean up the location flow in my Android app, which uses Fragments, Koin, Navigation Component, and Google Places Autocomplete.
I have a dialog provider that is reused by multiple fragments:
interface LocationDialogProvider { fun showManualLocationPrompt( onChooseManualLocation: () -> Unit, onDeny: () -> Unit ) fun showLocationUnavailableInfo(onPositiveClick: (() -> Unit)?) fun dismiss() }
I provide it with Koin:
val dialogModule = module { factory<DialogsProvider> { parameters -> BaseDialogsDelegate( activity = parameters.get<Activity>(), navControllerProvider = parameters.get<() -> NavController>() ) } factory<LocationDialogProvider> { parameters -> val activity = parameters.get<Activity>() val navControllerProvider = parameters.get<() -> NavController>() LocationDialogDelegate( baseDialogsProvider = get { parametersOf( activity, navControllerProvider ) } ) } }
Then I inject it into a base fragment:
abstract class LocationAwareFragment : Fragment() { protected val locationDialogsProvider: LocationDialogProvider by inject { parametersOf( requireActivity(), { findNavController() } ) } // ... }
The problem starts when I need to add Google Places Autocomplete for manual location selection. Currently, showManualLocationPrompt() exposes onChooseManualLocation, so the fragment can decide what happens when the user chooses manual location. For example, the fragment could call a placeAutocompleteLauncher. However, I am not sure where the placeAutocompleteLauncher should live. I see two possible approaches:
abstract class LocationAwareFragment : Fragment() { private val placeAutocompleteLauncher = registerForActivityResult(/* contract */) { result -> // handle selected place } fun showManualLocationPrompt() { locationDialogsProvider.showManualLocationPrompt( onChooseManualLocation = { placeAutocompleteLauncher.launch(/* request */) }, onDeny = { // handle deny } ) } }
Or I could hide this behind another delegate/orchestrator:
class ManualLocationFlowDelegate( private val dialogProvider: LocationDialogProvider, private val placePicker: GooglePlacesLocationPicker ) { fun showManualLocationPrompt(onDeny: () -> Unit) { dialogProvider.showManualLocationPrompt( onChooseManualLocation = { placePicker.open() }, onDeny = onDeny ) } }
In that case, the fragment would only call:
manualLocationFlowDelegate.showManualLocationPrompt(onDeny={})
Should the ActivityResultLauncher for Google Places Autocomplete stay directly inside the Fragment, because it is lifecycle-related. Or is it acceptable to move it into a delegate, as long as the delegate is created by the Fragment and registers the launcher early enough? Also, should LocationDialogProvider remain responsible only for showing dialogs, while another delegate coordinates the dialog + Places Autocomplete flow?