Skip to content

Commit

Permalink
[Student] Log mobius exceptions to Crashlytics (#236)
Browse files Browse the repository at this point in the history
  • Loading branch information
JordanMarshall authored Aug 14, 2019
1 parent d1f1af8 commit 1f415a4
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright (C) 2019 - present Instructure, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.instructure.student.mobius.common

import com.crashlytics.android.Crashlytics
import com.instructure.student.BuildConfig
import com.spotify.mobius.First
import com.spotify.mobius.Next
import com.spotify.mobius.android.AndroidLogger
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch

/**
* Intercepts exceptions in the mobius loop's update and init operations and logs them to Crashlytics.
* For debug builds the exception will be logged locally and then thrown.
*/
class MobiusExceptionLogger<MODEL, EVENT, EFFECT> : AndroidLogger<MODEL, EVENT, EFFECT>("Mobius") {
override fun afterUpdate(model: MODEL, event: EVENT, result: Next<MODEL, EFFECT>) {
if (BuildConfig.DEBUG) super.afterUpdate(model, event, result)
}

override fun afterInit(model: MODEL, result: First<MODEL, EFFECT>) {
if (BuildConfig.DEBUG) super.afterInit(model, result)
}

override fun beforeInit(model: MODEL) {
if (BuildConfig.DEBUG) super.beforeInit(model)
}

override fun beforeUpdate(model: MODEL, event: EVENT) {
if (BuildConfig.DEBUG) super.beforeUpdate(model, event)
}

override fun exceptionDuringInit(model: MODEL, exception: Throwable) {
if (BuildConfig.DEBUG) {
super.exceptionDuringInit(model, exception)
// Must throw as a separate message, otherwise Mobius might consume the exception
GlobalScope.launch(Dispatchers.Main) { throw exception }
} else {
Crashlytics.logException(exception)
}
}

override fun exceptionDuringUpdate(model: MODEL, event: EVENT, exception: Throwable) {
if (BuildConfig.DEBUG) {
super.exceptionDuringUpdate(model, event, exception)
// Must throw as a separate message, otherwise Mobius might consume the exception
GlobalScope.launch(Dispatchers.Main) { throw exception }
} else {
Crashlytics.logException(exception)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,19 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.crashlytics.android.Crashlytics
import com.instructure.interactions.FragmentInteractions
import com.instructure.interactions.Navigation
import com.instructure.student.BuildConfig
import com.instructure.student.mobius.common.*
import com.spotify.mobius.*
import com.spotify.mobius.android.MobiusAndroid
import com.spotify.mobius.android.runners.MainThreadWorkRunner
import com.spotify.mobius.functions.Consumer
import kotlinx.android.extensions.LayoutContainer
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch

abstract class MobiusFragment<MODEL, EVENT, EFFECT, VIEW : MobiusView<VIEW_STATE, EVENT>, VIEW_STATE> : Fragment(), FragmentInteractions {
var overrideInitModel: MODEL? = null
Expand Down Expand Up @@ -89,6 +94,7 @@ abstract class MobiusFragment<MODEL, EVENT, EFFECT, VIEW : MobiusView<VIEW_STATE
loop = Mobius.loop(update, effectHandler)
.effectRunner { MainThreadWorkRunner.create() }
.eventSources(globalEventSource, *eventSources.toTypedArray())
.logger(MobiusExceptionLogger())
.init(update::init)
controller = MobiusAndroid.controller(loop, overrideInitModel ?: makeInitModel())
}
Expand Down Expand Up @@ -195,9 +201,11 @@ abstract class EffectHandler<VIEW, EVENT, EFFECT> : CoroutineConnection<EFFECT>(

protected var consumer = ConsumerQueueWrapper<EVENT>()

private val connectionWrapper by lazy { ExceptionLoggerConnectionWrapper(this) }

override fun connect(output: Consumer<EVENT>): Connection<EFFECT> {
consumer.attach(output)
return this
return connectionWrapper
}

override fun dispose() {
Expand All @@ -213,4 +221,28 @@ abstract class EffectHandler<VIEW, EVENT, EFFECT> : CoroutineConnection<EFFECT>(
fun logEvent(eventName: String) {
// TODO
}

/**
* Catches exceptions in the [accept] function of the provided [connection] and logs them to Crashlytics.
* For debug builds the exception will be logged locally and then thrown.
*/
private class ExceptionLoggerConnectionWrapper<T>(private val connection: Connection<T>) : Connection<T> {
override fun accept(value: T) {
try {
connection.accept(value)
} catch (e: Throwable) {
if (BuildConfig.DEBUG) {
e.printStackTrace()
// Must throw as a separate message, otherwise Mobius might silently consume the exception
GlobalScope.launch(Dispatchers.Main) { throw e }
} else {
Crashlytics.logException(e)
}
}
}

override fun dispose() {
connection.dispose()
}
}
}

0 comments on commit 1f415a4

Please sign in to comment.