How to catch an error in Jetpack compose? - kotlin

I tried to catch application errors in this way:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
try {
MyApp()
}
catch (t: Throwable) {
Text(t.message ?: "Error", fontSize = 24.sp)
Text(t.stackTraceToString(), fontFamily = FontFamily.Monospace)
}
}
}
}
But it does not compile:
Try catch is not supported around composable function invocations.
WTF?
What else should I use?

#Composable
fun MyScreen() {
val data = try {
loadData()
} catch (error: Throwable) {
// Handle the error here
null
}
if (data != null) {
// Render the data
} else {
// Render an error message or retry button
}
}
To use the catch operator, you can call it on a Composable function and pass in a lambda function to handle the error. The lambda function should take a single parameter of type Throwable, which represents the error that occurred. Inside the lambda function, you can perform any necessary error handling, such as displaying an error message or retrying the failed operation.

Related

What is the best way to get data from an API using retrofit when something needs to wait on that data?

I have a retrofit API call but am having trouble getting the data out of it:
This is in a class file that's not a viewModel or Fragment. It's called from the apps main activity view model. I need to be able to get the data from the API and wait for some processing to be done on it before returning the value back the view model. Newer to kotlin and struggling with all the watchers and async functions. The result of this an empty string is the app crashes, because it's trying to access data before it has a value.
From class getData which is not a fragment
private lateinit var data: Data
fun sync(refresh: Boolean = false): List<String> {
var info = emptyList<String>
try {
getData(::processData, ::onFailure)
info = data.info
} catch(e: Throwable){
throw Exception("failed to get data")
}
}
}
return info
}
fun getData(
onSuccess: KFunction1<ApiResponse>?, Unit>,
onFailed: KFunction1<Throwable, Unit>
) {
val client = ApiClient().create(Service.RequestData)
val request = client.getData()
request.enqueue(object : Callback<ApiResponse> {
override fun onResponse(
call: Call<ApiResponse>,
response: Response<ApiResponse>
) {
onSuccess(response.body())
}
override fun onFailure(call: Call<RegistryResponse<GlobalLanguagePack>>, t: Throwable) {
onFailed(Exception("failed to get data"))
}
})
}
private fun processData(body: ApiResponse?) {
requireNotNull(body)
data = body.data
}
```
From appViewModel.kt:
```
fun setUpStuff(context: Context, resources: AppResources) = viewModelScope.launch {
val stuff = try {
getData.sync()
} catch (e: Exception) {
return#launch
}
if (stuff.isEmpty()) return#launch
}
```

How to block a suspend function that is calling firebase

In my AuthRepository I'm trying to make a login function that will run asynchronously in the main thread, I'm using firebase auth to do so and a sealed class AuthResult to handle the result state.
My problem is that when I try to return the AuthResult state it will be null because I return the var authResult: AuthResult = null variable before the .addOnCompleteListener is finished...
Here is my function:
override suspend fun login(email: String, password: String): AuthResult {
return try {
var authResult: AuthResult? = null
firebaseAuth.signInWithEmailAndPassword(email, password)
.addOnCompleteListener { task ->
if (task.isSuccessful) {
task.result.user?.let {
authResult = AuthResult.Success(it)
}
} else {
// exception
authResult = AuthResult.Error(message = "An error occurred", task.exception)
}
}
authResult ?: AuthResult.Error(message = "An error occurred null")
} catch (e: Exception) {
AuthResult.Error("An error has occurred", e)
}
}
I call this function using invoke usecase in my LoginViewModel:
private val _state = MutableStateFlow<AuthResult?>(null)
val state: StateFlow<AuthResult?> = _state
fun login(email: String, password: String) {
viewModelScope.launch(Dispatchers.IO) {
_state.value = loginUseCase(email, password)
}
}
so considering my problem state will be:
AuthResult.Error(message = "An error occurred null")
what im trying to accomplish is blocking the suspend fun login(...) until .addOnCompleteListener{} is finished...
Was missing a dependency so I couldn't use await():
add this:
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.6.0'
then add .await() after .addOnCompleteListener {}:
firebaseAuth.signInWithEmailAndPassword(email, password)
.addOnCompleteListener { task ->
...
}.await()

How to handle Kotlin Jetpack Paging 3 exceptions?

I am new to kotlin and jetpack, I am requested to handle errors (exceptions) coming from the PagingData, I am not allowed to use Flow, I am only allowed to use LiveData.
This is the Repository:
class GitRepoRepository(private val service: GitRepoApi) {
fun getListData(): LiveData<PagingData<GitRepo>> {
return Pager(
// Configuring how data is loaded by adding additional properties to PagingConfig
config = PagingConfig(
pageSize = 20,
enablePlaceholders = false
),
pagingSourceFactory = {
// Here we are calling the load function of the paging source which is returning a LoadResult
GitRepoPagingSource(service)
}
).liveData
}
}
This is the ViewModel:
class GitRepoViewModel(private val repository: GitRepoRepository) : ViewModel() {
private val _gitReposList = MutableLiveData<PagingData<GitRepo>>()
suspend fun getAllGitRepos(): LiveData<PagingData<GitRepo>> {
val response = repository.getListData().cachedIn(viewModelScope)
_gitReposList.value = response.value
return response
}
}
In the Activity I am doing:
lifecycleScope.launch {
gitRepoViewModel.getAllGitRepos().observe(this#PagingActivity, {
recyclerViewAdapter.submitData(lifecycle, it)
})
}
And this is the Resource class which I created to handle exceptions (please provide me a better one if there is)
data class Resource<out T>(val status: Status, val data: T?, val message: String?) {
companion object {
fun <T> success(data: T?): Resource<T> {
return Resource(Status.SUCCESS, data, null)
}
fun <T> error(msg: String, data: T?): Resource<T> {
return Resource(Status.ERROR, data, msg)
}
fun <T> loading(data: T?): Resource<T> {
return Resource(Status.LOADING, data, null)
}
}
}
As you can see I am using Coroutines and LiveData. I want to be able to return the exception when it occurs from the Repository or the ViewModel to the Activity in order to display the exception or a message based on the exception in a TextView.
Your GitRepoPagingSource should catch retryable errors and pass them forward to Paging as a LoadResult.Error(exception).
class GitRepoPagingSource(..): PagingSource<..>() {
...
override suspend fun load(..): ... {
try {
... // Logic to load data
} catch (retryableError: IOException) {
return LoadResult.Error(retryableError)
}
}
}
This gets exposed to the presenter-side of Paging as LoadState, which can be reacted to via LoadStateAdapter, .addLoadStateListener, etc as well as .retry. All of the presenter APIs from Paging expose these methods, such as PagingDataAdapter: https://developer.android.com/reference/kotlin/androidx/paging/PagingDataAdapter
You gotta pass your error handler to the PagingSource
class MyPagingSource(
private val api: MyApi,
private val onError: (Throwable) -> Unit,
): PagingSource<Int, MyModel>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, YourModel> {
try {
...
} catch(e: Exception) {
onError(e) // <-- pass your error listener here
}
}
}

RxJava2 Flowable onErrorReturn not called

Say I need to wrap BroadcastReceiver with Flowable:
Flowable
.create<Boolean>({ emitter ->
val broadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
throw RuntimeException("Test exception")
}
}
application.registerReceiver(broadcastReceiver, IntentFilter(LocationManager.PROVIDERS_CHANGED_ACTION))
}, BackpressureStrategy.MISSING)
.onErrorReturn { false }
Then I need to catch any exceptions thrown inside Flowable in one single place.
I supposed onErrorReturn should be able to catch that throw RuntimeException("Test exception") inside broadcastReceiver but it doesn't catch that exception and app crashes.
Certainly, I can wrap anything inside BroadcastReceiver with try/catch. But actually, I have a lot of source code there so that adding try/catch makes source code quite messy.
Is there any way to catch all the exceptions thrown in any line inside Flowable in one single place?
In case of Flowable#create() to follow contract of Flowable if you have error and want to pass it through stream, you need to catch it and call emitter.onError(). If you do that, Flowable.onErrorReturn() starts work as expected.
To properly register/unregister BroadcastReceiver and handle exceptions you can use that approach
Flowable
.create<Boolean>({ emitter ->
val broadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
try {
throw RuntimeException("Test exception")
} catch(e: Throwable) {
emitter.tryOnError(e)
}
}
}
try {
application.registerReceiver(broadcastReceiver, IntentFilter(LocationManager.PROVIDERS_CHANGED_ACTION))
emitter.setCancellable {
application.unregisterReceiver(broadcastReceiver)
}
} catch (e: Throwable) {
emitter.tryOnError(e)
}
}, BackpressureStrategy.MISSING)
.onErrorReturn { false }

subscribe function is not working

I am trying the basics of RxJava2.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_vogella)
setSupportActionBar(toolbar)
val todoObserverable= createObservable();
try {
todoObserverable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe ({ t-> Log.e(TAG,t.title)}, {e-> Log.e(TAG,e.localizedMessage)})
}catch (e:Exception){
e.printStackTrace()
}
}
get observable function:
fun createObservable():Observable<Book>{
val bookObservable: Observable<Book> = Observable.create { object :ObservableOnSubscribe<Book>{
override fun subscribe(emitter: ObservableEmitter<Book>) {
Log.e(TAG,"anc")
try {
val bookArrayList:ArrayList<Book> = ArrayList()
val bookOne= Book("XYZ")
val bookTwo= Book("ANC")
val bookThree= Book("3ewrXYZ")
val bookFour= Book("XwerweYZ")
bookArrayList.add(bookOne)
bookArrayList.add(bookTwo)
bookArrayList.add(bookThree)
bookArrayList.add(bookFour)
for (todo in bookArrayList){
emitter.onNext(todo)
Log.e(TAG,"on next")
}
emitter.onComplete()
}catch (e:Exception){
e.printStackTrace()
}
}
}
}
return bookObservable;
}
But I am unable to print the title of the book. It is not giving me any kind of error or exception.
I tried to debug the createObservable() but curser is not going inside the subscribe function.
Any hint will be helpful.
Observable.create { object :ObservableOnSubscribe<Book>{ - This essentially creates a ObservableOnSubscribe within a ObservableOnSubscribe. The object declaration is redundant or you can remove the lambda definition. (Observable.create(object : ETC))