Michael Langford
10/28/2021, 7:00 PMcomposable ("/EntryEditor/BTypeEditor/${logEntryId}") {
val existing = it.arguments?.getString(logEntryIdentifierName)
EventEditorBType(navController = navController, eventStoreModel = eventStoreModel, existing?.let{BTypeIdentifier(value = it)})
}
This successfully lets us edit new log items as well as select existing log items via nested navigation.
Here is where the problem hits.
When editing new log items, the log items only exist in the ViewModel of the EntryEditor as they aren't saved to the flow store, or the backend, so "deeper" events can't reference the "shallower" events by just parsing the route when the shallower events aren't yet saved to the eventStoreModel. We do need to not save the events until the user confirms they've entered everything to both match iOS and meet scientific needs of the tool.
E.g.
User hits "New BType" Floating Action Button on main screen.
User partially enters in BType event. Hits "previous BTypeEvent" button on this Btype event.
This launches a screen kinda like the main screen, except it only shows BType events. It does also have a + button.
If the user selects an event, how do we easily pass this selected event back to the first editor screen? it's easy enough to go off the route to get the logEntryId in the case where the toplevel BTypeEvent is saved to the event store, however, when a new BTypeEvent event is being recorded, this avenue isn't available, as the event isn't yet in the event store.
This problem also has to be "technically infinitely scalable" but de facto, like 8 levels deep scalable. A person could start to record a current BType record, start storing a new BType record about a past report on this topic, and in that, start storing a past BType record on this item.
In iOS, this is easy, we don't use path based routing, so we can just push a list of BTypeEvents with a + button on it, and if the user hits plus, we let them record the event, save it, etc, then when we return, we call a closure which was passed to the invoked editor when we navigated. We can't pass such closures with jetpack.
One idea I've had is to delve into the backstack to guess if you're saving events for "shallower" events by the path, but with this approach, I'm not sure how to get the reference to the previous screen from this and google is failing me there.
I've heard the approach to "share a view model"...and that feels like a bit of a stretch with how "nested" all of this is and how complex saving that view model wold be, as only parts of it would save at a time.
Any ideas folks?data class BTypeEntry(
var identifier: BTypeIdentifier,
var lookAndFeel: String = "",
var durationInMinutes: Int = 0,
var previousBEvent: BTypeIdentifier? = null,
var associatedAEvent: ATypeIdentifier? = null,
var lastIEvent: ITypeIdentifier? = null,
//like 10 more fields here, but stuff like strings, ints.
):EventLogEntry() {}
Ian Lake
10/28/2021, 7:39 PMMichael Langford
10/28/2021, 8:23 PMIan Lake
10/28/2021, 8:31 PMMichael Langford
10/28/2021, 8:31 PMIan Lake
10/28/2021, 8:32 PMMichael Langford
10/28/2021, 8:32 PMIan Lake
10/28/2021, 8:33 PMMichael Langford
10/28/2021, 8:34 PMIan Lake
10/28/2021, 8:36 PMMichael Langford
10/28/2021, 8:36 PMvar previousBEvent: BTypeIdentifier? = null,
Ian Lake
10/28/2021, 8:44 PMMichael Langford
10/28/2021, 8:46 PMIan Lake
10/28/2021, 8:47 PMMichael Langford
10/28/2021, 8:47 PMIan Lake
10/28/2021, 8:48 PMMichael Langford
10/28/2021, 8:49 PMIan Lake
10/28/2021, 8:49 PMMichael Langford
10/28/2021, 8:49 PMIan Lake
10/28/2021, 8:50 PMMichael Langford
10/28/2021, 8:51 PMIan Lake
10/28/2021, 8:52 PMMichael Langford
10/28/2021, 8:53 PMIan Lake
10/28/2021, 8:55 PMMichael Langford
10/28/2021, 8:56 PMIan Lake
10/28/2021, 9:01 PMMichael Langford
10/28/2021, 9:01 PMIan Lake
10/28/2021, 9:02 PMMichael Langford
10/28/2021, 9:03 PMIan Lake
10/28/2021, 9:06 PMMichael Langford
10/28/2021, 9:06 PMIan Lake
10/28/2021, 9:10 PMMichael Langford
10/28/2021, 9:12 PMIan Lake
10/28/2021, 9:12 PMMichael Langford
10/28/2021, 9:12 PMIan Lake
10/28/2021, 9:16 PMMichael Langford
10/28/2021, 9:17 PMIan Lake
10/28/2021, 9:18 PMMichael Langford
10/28/2021, 9:18 PMIan Lake
10/28/2021, 9:21 PMnavigation(startDestination="...", route = "creation_flow") {
composable(...) {}
composable(...) {}
composable(...) {}
}
That parent "creation_flow" gives you a scope for holding onto data that all your screen need access. A scope that is created when you first navigate to one of those destinations and that is automatically destroyed when the last of those screens is popped off the back stackMichael Langford
10/28/2021, 9:23 PMIan Lake
10/28/2021, 9:26 PMMichael Langford
10/28/2021, 9:28 PM"Deeper view when saved"
findNavController().previousBackStackEntry?.savedStateHandle?.set("key", data)
findNavController().popBackStack()
+
shallower view getting dataoverride fun onViewCreated(view: View, savedInstanceState: Bundle?) {
findNavController().currentBackStackEntry?.savedStateHandle?.getLiveData<String>("key")?.observe(viewLifecycleOwner) { data ->
// Do something with the data.
}
}
Ian Lake
10/28/2021, 9:35 PMnavigation(startDestination = "/EntryEditor/BTypeEditor?entryId=${logEntryId}&parentId={parentId}", route="creation_flow") {
composable ("/EntryEditor/BTypeEditor?entryId=${logEntryId}&parentId={parentId}") {
val existing = it.arguments?.getString(logEntryIdentifierName)
val parentId = it.arguments?.getString("parentId")
// This is how each destination gains access to the parent navigation graph entry
val parentEntry = remember {
navController.getBackStackEntry("creation_flow")
}
// And this ViewModel would be the same one from each destination
// You'd use viewModel instead of hiltViewModel if you weren't using Hilt
val creationFlowViewModel = hiltViewModel<CreationFlowViewModel>(
parentEntry
)
// Gets the existing entry using existing or creates a new one that has a parentId
// If both are null, we're on the root of a brand new BType
val bType = creationFlowViewModel.getOrCreateBType(exiting, parentId)
EventEditorBType(navController = navController, bType,
onSaved = { updatedBType ->
creationFlowViewModel.save(updatedBType)
},
onCreateNestedAType = {
// Navigate to the ATypeEditor, passing in this BType's ID as the parentId
navController.navigate("/EntryEditor/ATypeEditor?parentId={bType.id}")
)
}
CreationFlowViewModel
, which could be implemented like:
@HiltViewModel
class CreationFlowViewModel @Inject constructor(
private val eventStoreModel: EventStoreModel
) : ViewModel() {
// You probably want a better data type than this :)
val tempBTypeStore = mapOf<String, BType>()
val tempBTypeParentMap = mapOf<String, String>()
fun getOrCreateBType(existing: String?, parentid: String?): BType {
if (existing != null) {
val existingBType = eventStoreModel.getBType(existing)
if (existingBType != null) {
return existingBType;
}
}
val newBType = BType(UUID.randomUUID())
if (parentId != null) {
tempBTypeParentMap[newBType.id] = parentId
}
tempBTypeStore[newBType.id] = newBType
return newBType
}
fun save(bType: BType) {
val existing = eventStoreModel.getBType(existing)
if (existing != null) {
// This is an edit case, we can directly save it
eventStoreModel.update(bType)
} else {
if (tempBTypeParent[bType.id] == null) {
// This is the root item being saved, so we
// can save the BType - ideally, your better data
// structure can do this efficiently
eventStoreModel.add(bType)
} else {
// This isn't the root node, so we just update
// our temp copy
tempBTypeStore[bType.id] = bType
}
}
}
}
Michael Langford
10/29/2021, 1:19 PMgetBackStackEntry()
returns the topmost instance from the stack.