Skip to content

Commit

Permalink
Refactoring FormulaAndroid slightly and increasing code-coverage. (#335)
Browse files Browse the repository at this point in the history
  • Loading branch information
Laimiux authored Jan 25, 2024
1 parent 406d7dc commit 5ea7759
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 49 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.instacart.formula

import android.app.Application
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class FormulaAndroidTest {

@Test fun `crashes if initialized twice`() {

try {
val result = runCatching {
val context = ApplicationProvider.getApplicationContext<Application>()
FormulaAndroid.init(context) {}
FormulaAndroid.init(context) {}
}
val error = result.exceptionOrNull()?.message
Truth.assertThat(error).isEqualTo("can only initialize the store once.")
} finally {
FormulaAndroid.reset()
}
}

@Test fun `crashes if accessed before initialization`() {
val result = runCatching {
FormulaAndroid.onBackPressed(ActivityUpdateTest.TestActivity())
}
val errorMessage = result.exceptionOrNull()?.message
Truth.assertThat(errorMessage).isEqualTo(
"Need to call FormulaAndroid.init() from your Application."
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ import com.instacart.formula.android.FragmentEnvironment
import com.instacart.formula.android.internal.ActivityStoreFactory
import com.instacart.formula.android.internal.AppManager
import com.instacart.formula.android.FragmentKey
import com.instacart.formula.android.internal.FormulaFragmentDelegate
import java.lang.IllegalStateException

object FormulaAndroid {

private var application: Application? = null
private var appManager: AppManager? = null
private var fragmentEnvironment: FragmentEnvironment? = null

/**
* Initializes Formula Android integration. Should be called within [Application.onCreate].
Expand All @@ -40,39 +40,22 @@ object FormulaAndroid {

this.application = application
this.appManager = appManager
FormulaFragmentDelegate.appManager = appManager
FormulaFragmentDelegate.fragmentEnvironment = fragmentEnvironment
}

/**
* Initializes Formula Android integration. Should be called within [Application.onCreate].
*
* @param logger A logger for debug Formula Android events.
* @param onFragmentError A global handler for fragment errors. Override this to log the crashes.
*/
fun init(
application: Application,
logger: ((String) -> Unit)? = null,
onFragmentError: (FragmentKey, Throwable) -> Unit = { _, it -> throw it },
activities: ActivityConfigurator.() -> Unit
) {
val fragmentEnvironment = FragmentEnvironment(logger ?: {}, onFragmentError)
init(application, fragmentEnvironment, activities)
this.fragmentEnvironment = fragmentEnvironment
}

/**
* Call this method in [FragmentActivity.onCreate] before calling [FragmentActivity.super.onCreate]
*/
fun onPreCreate(activity: FragmentActivity, savedInstance: Bundle?) {
managerOrThrow(activity).onPreCreate(activity, savedInstance)
appManagerOrThrow().onPreCreate(activity, savedInstance)
}

/**
* Call this method in [FragmentActivity.onActivityResult]
*/
fun onActivityResult(activity: FragmentActivity, requestCode: Int, resultCode: Int, data: Intent?) {
val result = ActivityResult(requestCode, resultCode, data)
managerOrThrow(activity).onActivityResult(activity, result)
appManagerOrThrow().onActivityResult(activity, result)
}

/**
Expand All @@ -91,21 +74,30 @@ object FormulaAndroid {
* ```
*/
fun onBackPressed(activity: FragmentActivity): Boolean {
return managerOrThrow(activity).onBackPressed(activity)
}

private fun managerOrThrow(activity: FragmentActivity): AppManager {
return appManager ?: throw IllegalStateException("call FormulaAndroid.init() from your Application: $activity")
return appManagerOrThrow().onBackPressed(activity)
}

/**
* Used in testing to clear current store manager.
*/
@VisibleForTesting fun reset() {
val app = application ?: throw IllegalStateException("not initialized")
val app = ensureInitialized(application)
app.unregisterActivityLifecycleCallbacks(appManager)

application = null
appManager = null
}


internal fun appManagerOrThrow(): AppManager {
return ensureInitialized(appManager)
}

internal fun fragmentEnvironment(): FragmentEnvironment {
return ensureInitialized(fragmentEnvironment)
}

private fun <T : Any> ensureInitialized(variable: T?): T {
return checkNotNull(variable) { "Need to call FormulaAndroid.init() from your Application." }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.instacart.formula.FormulaAndroid
import com.instacart.formula.android.internal.FormulaFragmentDelegate
import com.instacart.formula.android.internal.getFormulaFragmentId
import java.lang.Exception
Expand Down Expand Up @@ -33,7 +34,7 @@ class FormulaFragment : Fragment(), BaseFormulaFragment<Any> {
}

private val environment: FragmentEnvironment
get() = FormulaFragmentDelegate.fragmentEnvironment()
get() = FormulaAndroid.fragmentEnvironment()

private val fragmentDelegate: FragmentEnvironment.FragmentDelegate
get() = environment.fragmentDelegate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ internal class CompositeBinding<ParentComponent, ScopedComponent>(
val component = state.component
if (component != null) {
val childInput = Input(
input.environment,
component.component,
input.activeFragments,
input.onInitializeFeature,
environment = input.environment,
component = component.component,
activeFragments = input.activeFragments,
onInitializeFeature = input.onInitializeFeature,
)
bindings.forEachIndices {
it.bind(context, childInput)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
package com.instacart.formula.android.internal

import com.instacart.formula.FormulaAndroid
import com.instacart.formula.FormulaAndroid.fragmentEnvironment
import com.instacart.formula.android.FormulaFragment
import com.instacart.formula.android.FragmentEnvironment
import com.instacart.formula.android.FragmentKey
import com.instacart.formula.android.ViewFactory

internal object FormulaFragmentDelegate {
var appManager: AppManager? = null
var fragmentEnvironment: FragmentEnvironment? = null

fun viewFactory(fragment: FormulaFragment): ViewFactory<Any>? {
val appManager = checkNotNull(appManager) { "FormulaAndroid.init() not called." }
val appManager = FormulaAndroid.appManagerOrThrow()

val activity = fragment.activity ?: run {
fragmentEnvironment().logger("FormulaFragment has no activity attached: ${fragment.getFragmentKey()}")
Expand All @@ -30,9 +27,4 @@ internal object FormulaFragmentDelegate {
}
return viewFactory
}


fun fragmentEnvironment(): FragmentEnvironment {
return checkNotNull(fragmentEnvironment) { "FormulaAndroid.init() not called." }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.instacart.formula.compose.stopwatch
import android.app.Application
import android.util.Log
import com.instacart.formula.FormulaAndroid
import com.instacart.formula.android.FragmentEnvironment

class StopwatchApp : Application() {

Expand All @@ -11,9 +12,11 @@ class StopwatchApp : Application() {

FormulaAndroid.init(
application = this,
onFragmentError = { contract, error ->
Log.e("StopwatchApp", "fragment crashed", error)
},
fragmentEnvironment = FragmentEnvironment(
onScreenError = { key, error ->
Log.e("StopwatchApp", "fragment crashed", error)
}
),
activities = {
activity<StopwatchActivity> {
store(
Expand Down
9 changes: 6 additions & 3 deletions samples/todoapp/src/main/java/com/examples/todoapp/TodoApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.app.Application
import android.util.Log
import com.examples.todoapp.tasks.TaskListFeatureFactory
import com.instacart.formula.FormulaAndroid
import com.instacart.formula.android.FragmentEnvironment

class TodoApp : Application() {

Expand All @@ -12,9 +13,11 @@ class TodoApp : Application() {

FormulaAndroid.init(
application = this,
onFragmentError = { contract, error ->
Log.e("TodoApp", "fragment crashed", error)
},
fragmentEnvironment = FragmentEnvironment(
onScreenError = { _, error ->
Log.e("TodoApp", "fragment crashed", error)
}
),
activities = {
activity<TodoActivity> {
val component = TodoAppComponent(this)
Expand Down

0 comments on commit 5ea7759

Please sign in to comment.