Skip to content

Commit

Permalink
Decouple crash reporter and add doc
Browse files Browse the repository at this point in the history
  • Loading branch information
Atick authored and atick-faisal committed Dec 29, 2024
1 parent cfc41fa commit 9655b83
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 9 deletions.
46 changes: 46 additions & 0 deletions core/ui/src/main/kotlin/dev/atick/core/ui/utils/CrashReporter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright 2024 Atick Faisal
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package dev.atick.core.ui.utils

import com.google.firebase.crashlytics.ktx.crashlytics
import com.google.firebase.ktx.Firebase

/**
* Interface for reporting exceptions.
*/
interface CrashReporter {
/**
* Reports an exception.
*
* @param throwable The exception to be reported.
*/
fun reportException(throwable: Throwable)
}

/**
* Implementation of [CrashReporter] that uses Firebase Crashlytics.
*/
class FirebaseCrashReporter() : CrashReporter {
/**
* Reports an exception to Firebase Crashlytics.
*
* @param throwable The exception to be reported.
*/
override fun reportException(throwable: Throwable) {
Firebase.crashlytics.recordException(throwable)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,27 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.google.firebase.crashlytics.ktx.crashlytics
import com.google.firebase.ktx.Firebase
import dev.atick.core.ui.components.JetpackOverlayLoadingWheel
import dev.atick.core.utils.OneTimeEvent
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch

/**
* A composable function that represents a stateful UI component.
*
* @param T The type of the data.
* @param state The current state of the UI.
* @param onShowSnackbar A suspend function to show a snackbar with a message and an optional action.
* @param crashReporter An optional crash reporter to report exceptions (default is FirebaseCrashReporter).
* @param content A composable function that defines the UI content based on the state data.
*/
@Composable
fun <T : Any> StatefulComposable(
state: UiState<T>,
onShowSnackbar: suspend (String, String?) -> Boolean,
crashReporter: CrashReporter = FirebaseCrashReporter(),
content: @Composable (T) -> Unit,
) {
content(state.data)
Expand All @@ -54,24 +62,42 @@ fun <T : Any> StatefulComposable(
state.error.getContentIfNotHandled()?.let { error ->
LaunchedEffect(onShowSnackbar) {
val report = onShowSnackbar(error.message.toString(), "REPORT")
if (report) Firebase.crashlytics.recordException(error)
if (report) crashReporter.reportException(error)
}
}
}

/**
* Data class representing the state of the UI.
*
* @param T The type of the data.
* @property data The current data of the UI.
* @property loading A flag indicating whether the UI is in a loading state.
* @property error An event representing an error that may have occurred.
*/
data class UiState<T : Any>(
val data: T,
val loading: Boolean = false,
val error: OneTimeEvent<Throwable?> = OneTimeEvent(null),
)

/**
* Extension function to update the state of a MutableStateFlow.
*
* @param T The type of the data.
* @param update A function to update the data.
*/
inline fun <T : Any> MutableStateFlow<UiState<T>>.updateState(update: T.() -> T) {
update { UiState(update(it.data)) }
}

// TODO: context params will be available after kotlin 2.2
// until then, have to pass scope as a param
// context(ViewModel)
/**
* Extension function to update the state of a MutableStateFlow with a suspend operation.
*
* @param T The type of the data.
* @param scope The CoroutineScope to launch the operation.
* @param operation A suspend function that returns a Result of the data.
*/
inline fun <reified T : Any> MutableStateFlow<UiState<T>>.updateStateWith(
scope: CoroutineScope,
crossinline operation: suspend () -> Result<T>,
Expand Down Expand Up @@ -107,9 +133,13 @@ inline fun <reified T : Any> MutableStateFlow<UiState<T>>.updateStateWith(
}
}

// TODO: context params will be available after kotlin 2.2
// until then, have to pass scope as a param
// context(ViewModel)
/**
* Extension function to update the state of a MutableStateFlow with a suspend operation that returns Unit.
*
* @param T The type of the data.
* @param scope The CoroutineScope to launch the operation.
* @param operation A suspend function that returns a Result of Unit.
*/
inline fun <T : Any> MutableStateFlow<UiState<T>>.updateWith(
scope: CoroutineScope,
crossinline operation: suspend () -> Result<Unit>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@

package dev.atick.core.ui.utils

/**
* Data class representing the state of a text field.
*
* @property value The current value of the text field.
* @property errorMessage An optional error message associated with the text field.
*/
data class TextFiledData(
val value: String,
val errorMessage: String? = null,
Expand Down

0 comments on commit 9655b83

Please sign in to comment.