[Feature] Custom Named Exception (#386)

This commit is contained in:
Albert Lo 2022-08-19 14:03:32 -07:00 коммит произвёл Mohtasim
Родитель 0032939274
Коммит fdb97aa35f
7 изменённых файлов: 111 добавлений и 24 удалений

Просмотреть файл

@ -0,0 +1,38 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.android.communication.ui.calling;
import androidx.annotation.NonNull;
/**
* Defines the base type of custom Exception that can be thrown by this Library.
*/
public final class CallCompositeException extends RuntimeException {
/**
* Constructs a new Calling Composite exception with the specified error message and cause. Note
* that the error message associated with "cause" is not automatically incorporated into this
* exception's error message.
*
* @param errorMessage - the error message. The error message can be retrieved by the
* getMessage() method
* @param cause - the cause (which is saved for later retrieval by the getCause() method). A
* null value is permitted, and indicates that the cause is non-existent or unknown.
*/
public CallCompositeException(final String errorMessage, @NonNull final Throwable cause) {
super(errorMessage, cause);
}
/**
* Constructs a new Calling Composite exception with the specified cause and message of
* (cause==null ? null : cause.toString()) (which typically contains the class and detail message
* of cause). This constructor is useful for exceptions that are little more than wrappers for
* other throwables.
*
* @param cause - the cause (which is saved for later retrieval by the Throwable.getCause() method).
* A null value is permitted, and indicates that the cause is nonexistent or unknown.
*/
public CallCompositeException(final Throwable cause) {
super(cause);
}
}

Просмотреть файл

@ -3,10 +3,11 @@
package com.azure.android.communication.ui.calling.configuration
import com.azure.android.communication.ui.calling.CallCompositeException
import com.azure.android.communication.ui.calling.configuration.events.CallCompositeEventsHandler
import com.azure.android.communication.ui.calling.models.CallCompositeLocalOptions
import com.azure.android.communication.ui.calling.models.CallCompositeLocalizationOptions
import java.lang.RuntimeException
import java.lang.IllegalStateException
internal class CallCompositeConfiguration {
var themeConfig: Int? = null
@ -43,8 +44,9 @@ internal class CallCompositeConfiguration {
// Gets a config by it's ID
// May return null if the Configuration becomes garbage collected
fun getConfig(id: Int): CallCompositeConfiguration = configs[id]
?: throw RuntimeException(
"This ID is not valid, and no entry exists in the map. Please file a bug, this is an error in the composite"
?: throw CallCompositeException(
"This ID is not valid, and no entry exists in the map. Please file a bug, this is an error in the composite",
IllegalStateException()
)
// Check if config exists

Просмотреть файл

@ -6,6 +6,7 @@ package com.azure.android.communication.ui.calling.presentation
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import com.azure.android.communication.ui.R
import com.azure.android.communication.ui.calling.CallCompositeException
import com.azure.android.communication.ui.calling.configuration.CallCompositeConfiguration
import com.azure.android.communication.ui.calling.di.DependencyInjectionContainer
import com.azure.android.communication.ui.calling.di.DependencyInjectionContainerImpl
@ -18,7 +19,6 @@ import com.azure.android.communication.ui.calling.service.sdk.CallingSDK
import com.azure.android.communication.ui.calling.utilities.CoroutineContextProvider
import java.lang.IllegalArgumentException
import java.lang.RuntimeException
/**
* ViewModel for the CallCompositeActivity
@ -35,24 +35,26 @@ internal class DependencyInjectionContainerHolder(
private val customVideoStreamRendererFactory: VideoStreamRendererFactory?,
private val customCoroutineContextProvider: CoroutineContextProvider?
) : AndroidViewModel(application) {
companion object {
private const val commonMessage =
"Please ensure that you have set a valid instanceId before retrieving the container."
}
// Instance ID to locate Configuration. -1 is invalid.
var instanceId: Int = -1
set(value) {
if (!CallCompositeConfiguration.hasConfig(value)) {
throw IllegalArgumentException(
"Configuration with instanceId:$value does not exist. " +
"Please ensure that you have set a valid instanceId before retrieving the container."
)
val exceptionMessage =
"Configuration with instanceId:$value does not exist. $commonMessage"
throw CallCompositeException(exceptionMessage, IllegalArgumentException(exceptionMessage))
}
field = value
}
val container: DependencyInjectionContainer by lazy {
if (instanceId == -1) {
throw RuntimeException(
"Will not be able to locate a Configuration for instanceId: -1. " +
"Please ensure that you have set instanceId before retrieving the container."
)
val exceptionMessage =
"Will not be able to locate a Configuration for instanceId: -1. $commonMessage"
throw CallCompositeException(exceptionMessage, IllegalStateException(exceptionMessage))
}
// Generate a new instance

Просмотреть файл

@ -25,6 +25,7 @@ import android.os.Bundle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.coroutineScope
import com.azure.android.communication.ui.calling.CallCompositeException
import kotlinx.coroutines.launch
import java.lang.IllegalArgumentException
import com.azure.android.communication.ui.calling.redux.state.PermissionStatus
@ -79,7 +80,7 @@ internal class AudioSessionManager(
fun onStart(activity: Activity) {
if (activity !is LifecycleOwner) {
throw IllegalArgumentException("Activity must be a LifecycleOwner)")
throw CallCompositeException("Activity must be a LifecycleOwner", IllegalArgumentException())
}
(activity as LifecycleOwner).lifecycle.coroutineScope.launch {
// On first launch we need to init the redux-state, check Bluetooth and Headset status

Просмотреть файл

@ -5,6 +5,7 @@ package com.azure.android.communication.ui.calling.redux
import android.os.Handler
import android.os.Looper
import com.azure.android.communication.ui.calling.CallCompositeException
import com.azure.android.communication.ui.calling.redux.action.Action
import com.azure.android.communication.ui.calling.redux.reducer.Reducer
import kotlinx.coroutines.CoroutineExceptionHandler
@ -14,8 +15,6 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.MutableStateFlow
import kotlin.coroutines.CoroutineContext
internal class AppStoreException(msg: String, e: Throwable? = null) : Exception(msg, e)
internal class AppStore<S>(
initialState: S,
private val reducer: Reducer<S>,
@ -25,7 +24,7 @@ internal class AppStore<S>(
// Any exceptions encountered in the reducer are rethrown to crash the app and not get silently ignored.
private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
Handler(Looper.getMainLooper()).postAtFrontOfQueue {
throw AppStoreException("Exception while reducing state", throwable)
throw CallCompositeException("App store exception while reducing state", throwable)
}
// At this point (after an exception) we don't want to accept any more work.
scope.cancel()

Просмотреть файл

@ -20,6 +20,7 @@ import com.azure.android.communication.calling.JoinMeetingLocator
import com.azure.android.communication.calling.TeamsMeetingLinkLocator
import com.azure.android.communication.calling.VideoDevicesUpdatedListener
import com.azure.android.communication.calling.VideoOptions
import com.azure.android.communication.ui.calling.CallCompositeException
import com.azure.android.communication.ui.calling.configuration.CallCompositeConfiguration
import com.azure.android.communication.ui.calling.configuration.CallConfiguration
import com.azure.android.communication.ui.calling.configuration.CallType
@ -54,18 +55,23 @@ internal class CallingSDKWrapper(
private val callConfig: CallConfiguration
get() {
if (configuration.callConfig == null)
throw IllegalStateException("Call configurations are not set")
return configuration.callConfig!!
try {
return configuration.callConfig!!
} catch (ex: Exception) {
throw CallCompositeException(
"Call configurations are not set",
IllegalStateException()
)
}
}
private val call: Call
get() {
if (nullableCall == null)
throw IllegalStateException("Call is not started")
return nullableCall!!
try {
return nullableCall!!
} catch (ex: Exception) {
throw CallCompositeException("Call is not started", IllegalStateException())
}
}
override fun getRemoteParticipantsMap(): Map<String, RemoteParticipant> =

Просмотреть файл

@ -0,0 +1,39 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.android.communication.ui.configuration
import com.azure.android.communication.ui.calling.CallCompositeException
import com.azure.android.communication.ui.calling.configuration.CallCompositeConfiguration
import org.hamcrest.MatcherAssert.assertThat
import org.junit.Assert.assertThrows
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.junit.MockitoJUnitRunner
@RunWith(MockitoJUnitRunner::class)
internal class CallCompositeExceptionTest {
@Test
fun callCompositeConfiguration_errorHandling() {
val classObj = CallCompositeException::class.java
CallCompositeConfiguration.putConfig(0, CallCompositeConfiguration())
val ex = assertThrows(classObj, ::getCallCompositeConfig)
assertThat(
"invalid type: ${ex.javaClass.simpleName}",
ex.javaClass.simpleName == classObj.simpleName
)
assertThat(
"invalid message, expecting: ${ex.message}",
ex.message?.startsWith("This ID is not valid, and no entry exists in the map") ?: false
)
assertThat(
"Invalid cause, expecting: ${ex.cause}",
ex.cause is IllegalStateException
)
}
private fun getCallCompositeConfig() = CallCompositeConfiguration.getConfig(1)
}