Blog Infos
Author
Published
Topics
Published
Topics

Day 13 [Code Diff]

Not a big change but pretty useful information for the user.

Progress by the end of day 13

Day 14 [Code Diff]
sealed interface MyDeckListAction {
    ...
    object ShowNoCardsToReviewInfo : MyDeckListAction
}
viewModelScope.launch {
      val action =
             if (model.cardsToReview <= 0) MyDeckListAction.ShowNoCardsToReviewInfo
             else MyDeckListAction.NavigateToPractice(model.deckId)
  
      _action.emit(action)
}

Progress by the end of day 14

Day 15 [Code Diff]
sealed interface CheckPracticeAnswerResponse {
    data class Correct(val isExactAnswer: Boolean) : CheckPracticeAnswerResponse
    object Wrong : CheckPracticeAnswerResponse
    
    fun isCorrect() = this is Correct
}

After that I defined a list of characters that I want to ignore when checking the answer. That means “How?” and “How” are treated as if they were the same thing.

private val charsToIgnore = Regex("[?!,.;\"']")
override fun checkAnswer(card: DeckCard, answer: String): CheckPracticeAnswerResponse {
    val normalizedAnswer = answer.normalize()
    val normalizedOutputs = card.outputs.normalize()

    if (normalizedAnswer.isBlank())
        return Wrong
    if (normalizedOutputs.any { it == normalizedAnswer })
        return Correct(isExactAnswer = card.outputs.any { it == answer })

    return Wrong
}

private fun String.normalize() = trim().replace(charsToIgnore, "")
private fun List<String>.normalize() = map { it.normalize() }
private val card1 = card(
    input = "Potrebbe aiutarmi, per favore?",
    outputs = listOf("Could you help me, please?"),
)

@Test
fun answerWithoutQuestionMarkIsCorrect() {
    val answer = "Could you help me, please"

    val result = useCase.checkAnswer(card1, answer)

    assertEquals(Correct(isExactAnswer = true), result)
}
  • It’s a red box if you get the answer wrong
  • It’s a green box if your answer is pretty close the the actual answer

Job Offers

Job Offers


    Senior Android Developer

    SumUp
    Berlin
    • Full Time
    apply now

    Senior Android Engineer

    Carly Solutions GmbH
    Munich
    • Full Time
    apply now

OUR VIDEO RECOMMENDATION

, ,

Boosting Compose UI from Sluggish to Snappy

Join us on an enlightening quest as we unravel the realm of Compose UI performance. With a multitude of tools at our disposal, the challenge lies in knowing where to start and how to choose.
Watch Video

Boosting Compose UI from Sluggish to Snappy

Akshay Chordiya & Tasha Ramesh
Android Developer & Android
Tinder

Boosting Compose UI from Sluggish to Snappy

Akshay Chordiya & ...
Android Developer & ...
Tinder

Boosting Compose UI from Sluggish to Snappy

Akshay Chordiya ...
Android Developer & Andro ...
Tinder

Jobs

The default transition for AnimatedVisibility expands/collapses the container vertically and horizontally but I only want it to expand/collapse vertically so I had to change the enter/exit transition.

@Composable
private fun InfoBox(state: PracticeState) {
    AnimatedVisibility(
        visible = state.infoText != null,
        enter = fadeIn() + expandIn(initialSize = { IntSize(it.width, 0) }),
        exit = shrinkOut(targetSize = { IntSize(it.width, 0) }) + fadeOut()
    ) {
        val bgColor = state.infoBackgroundColorRes ?: return@AnimatedVisibility

        Box(
            modifier = Modifier
                .fillMaxWidth()
                .background(colorResource(id = bgColor))
                .padding(horizontal = 12.dp, vertical = 20.dp)
        ) {
               ....
        }
    }
}

After 15 days, we finally have the first “usable” version of the app.

Progress by the end of day 15

 

Day 16 [Code Diff]
fun onAnswerChanged(answer: String) {
    if (!isAnswerFieldEnabled()) return
    state.answer = answer
}
TextField(
    ...
    modifier = Modifier
        .onKeyEvent { onKeyEvent(it.nativeKeyEvent) }
)

If the key event is different from ENTER I just ignore it, otherwise, I emit Unit to a channel.

fun onKeyEvent(keyEvent: NativeKeyEvent): Boolean {
    if (keyEvent.keyCode != KeyEvent.KEYCODE_ENTER) return false

    enterEventChannel.tryEmit(Unit)
    return true
}
private val enterEventChannel = MutableSharedFlow<Any>(
    extraBufferCapacity = 1,
    onBufferOverflow = BufferOverflow.DROP_OLDEST
)

viewModelScope.launch {
    enterEventChannel
        .debounce(KEY_INPUT_DEBOUNCE_DELAY)
        .collect { onContinue() }
}

Finally on the onContinue method I either load the next question or check the answer based in which state the screen is on.

fun onContinue() {
    if (state.infoText != null) {
        loadNextQuestion()
    } else {
        checkAnswer()
    }
}
class CheckPracticeAnswerUseCaseImpl @Inject constructor(
    private val wordDistanceCalculator: WordDistanceCalculator
)
normalizedOutputs.forEach { output ->
    val distance = wordDistanceCalculator.calculate(normalizedAnswer, output)
    val threshold = (output.length * WORD_DIFF_THRESHOLD).toInt()
    if (distance <= threshold) return Correct(isExactAnswer = false)
}

This algorithm is not perfect but I don’t see myself changing it anymore in the near future, this is the algorithm I had in mind when I described how the practice was going to work.

Progress by the end of day 16

 

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
Compose is a relatively young technology for writing declarative UI. Many developers don’t even…
READ MORE
blog
When it comes to the contentDescription-attribute, I’ve noticed a couple of things Android devs…
READ MORE
blog
In this article we’ll go through how to own a legacy code that is…
READ MORE
blog
Compose is part of the Jetpack Library released by Android last spring. Create Android…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu