HI everyone, I have a question. How do you handle ...
# appyx
o
HI everyone, I have a question. How do you handle async data loading in Appyx? I have a ClassroomNode which uses backstack which contains a LessonNode which uses the spotlight and gets id as a param and based on that id I want to load spotlight model from database. I could not really figure it out from the Cake example. (I would like to call a suspend function inside resolve)
Copy code
class ClassroomNode(
    buildContext: BuildContext,
    private val backStack: BackStack<ClassroomNavTarget> = BackStack(
        model = BackStackModel(
            initialTarget = ClassroomNavTarget.Classroom,
            savedStateMap = buildContext.savedStateMap,
        ),
        visualisation = { BackStackFader(it) }
    )
) : ParentNode<ClassroomNavTarget>(
    buildContext = buildContext,
    appyxComponent = backStack
) {
...
    override fun resolve(interactionTarget: ClassroomNavTarget, buildContext: BuildContext): Node =
        when (interactionTarget) {
...
            is ClassroomNavTarget.Lesson ->
                // I feel like I need to pass the spotlight model here
                // something like pseudo code below
                // val lessonCards = classroomRepository.getLessonCards(interactionTarget.id) // but it is async
                // val spotlight = Spotlight(model = SpotlightModel(initialItems = lessonCards))
                // LessonNode(buildContext, popBackStack = { backStack.pop() }, spotlight)
                LessonNode(buildContext, popBackStack = { backStack.pop() })
...
        }
}
Copy code
class LessonNode(
    buildContext: BuildContext,
    private val popBackStack: () -> Unit,
    private val spotlight: Spotlight<StudyCards> = Spotlight(
        model = SpotlightModel(
            items = initialItems,
            savedStateMap = null
        ),
        visualisation = { SpotlightStack3D(it) },
        gestureFactory = {
            SpotlightSlider.Gestures(
                transitionBounds = it,
                orientation = Orientation.Vertical,
                reverseOrientation = true,
            )
        },
    )
) : ParentNode<StudyCards>(
    buildContext = buildContext,
    appyxComponent = spotlight
) {
z
Hey @Ondrej Stanek! Thanks for the code snippet. Generally you would not do business logic or anything elaborate directly in
resolve
, just return a
Node
instance. If you have many dependencies you can benefit from having a
Builder
class with a dedicated
build
method. But this is secondary now. For your case, it already feels like your lesson cards fetching belongs as a responsibility to
LessonNode
rather than to
ClassroomNode
. Plus, you don’t even need to have all the items upfront. Instead, you could create
LessonNode
without async, and start the async operation inside of it. Then, when it completes you would trigger an
Update
operation on your
spotlight
instance: https://bumble-tech.github.io/appyx/components/spotlight/#update-elements Meanwhile you could show a loading spinner, and if you like, you can also customise the transitions for the incoming elements.
o
Thanks a lot for your answer Zsolt. Great work, I really enjoy using Appyx in my personal project. Your are right, and it indeed belongs inside the lesson node. I will try to do that. In the meantime I came up with different solution. By using the explicit navigation I do the database call there...
Copy code
// Navigator.kt
    fun goToLesson(lessonId: String) {
        lifecycleScope.launch {
            rootNode
                .goToMain()
                .goToClassroom()
                .goToLesson(lessonId)
        }
    }

// ClassroomNode.kt
suspend fun goToLesson(lessonId: String): LessonNode {
    val lessonCards = classroomRepository
        .getLessonCards(lessonId)
        .getOrElse {
            println("Error I should handle error state: $it")
            emptyList()
        }
        .map {lessonCardModel ->
            val (front, back) = lessonCardModel.text.split(QaCardDelimiter)
            StudyCards.ReverseCard(
                StudyCards.SimpleCard(front),
                StudyCards.SimpleCard(front),
            )
        }

    return attachChild {
        backStack.push(ClassroomNavTarget.Lesson(lessonId, lessonCards))
    }
}
z
Yep, that works just as well! Nice usage of the navigator pattern there 👏
(FYI not sure it’s intentional but you’re using
front
twice, and
back
0 times in your reverse card)
🤯 1
o
Haha, it is a typo. Thank you!