Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring FormulaAndroid slightly and increasing code-coverage. #335

Merged
merged 1 commit into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing this old init function

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
Loading