[REFACTOR] Moved fake API config from DevConfig to BuildConfig

Fixes #16
This commit is contained in:
Hossain Khan
2025-05-02 19:42:03 -04:00
parent b5da1d0262
commit 3c38cac8a0
8 changed files with 37 additions and 48 deletions
+1
View File
@@ -34,3 +34,4 @@ google-services.json
.DS_Store
app/debug/
app/release/
*.salive
+8 -11
View File
@@ -1,5 +1,3 @@
import java.util.Properties
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
@@ -21,15 +19,6 @@ android {
targetSdk = 35
versionCode = 2
versionName = "1.1.0"
// Read key or other properties from local.properties
val localProperties =
project.rootProject.file("local.properties").takeIf { it.exists() }?.inputStream()?.use {
Properties().apply { load(it) }
}
val apiKey = localProperties?.getProperty("SERVICE_API_KEY") ?: "MISSING-KEY"
buildConfigField("String", "SERVICE_API_KEY", "\"$apiKey\"")
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
@@ -57,6 +46,10 @@ android {
buildTypes {
release {
// Always force `USE_FAKE_API` to `false` for release builds
// See https://github.com/usetrmnl/trmnl-android/issues/16
buildConfigField("Boolean", "USE_FAKE_API", "false")
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
@@ -66,6 +59,10 @@ android {
}
debug {
// Allow developers to configure this value for debug builds
// Use fake API response for local development and testing purposes.
buildConfigField("Boolean", "USE_FAKE_API", "true")
signingConfig = signingConfigs.getByName("debug")
}
}
@@ -1,12 +0,0 @@
package ink.trmnl.android.data
/**
* Development configuration for the TRMNL app.
*/
object DevConfig {
/**
* Fake API response for local development and testing purposes.
* In debug builds, we use fake API responses.
*/
const val FAKE_API_RESPONSE = true
}
@@ -1,5 +1,6 @@
package ink.trmnl.android.data
import ink.trmnl.android.BuildConfig
import javax.inject.Inject
/**
@@ -12,10 +13,12 @@ class RepositoryConfigProvider
/**
* Indicates if the app should use fake data instead of real API responses.
*
* @return Boolean value from [DevConfig.FAKE_API_RESPONSE]
* @return Boolean value from [BuildConfig.USE_FAKE_API]
*/
val shouldUseFakeData: Boolean
get() {
return DevConfig.FAKE_API_RESPONSE
// To change this value, update the `buildConfigField` in the app's build.gradle file
// Or, change the value here for local development. Do not commit this change.
return BuildConfig.USE_FAKE_API
}
}
@@ -2,7 +2,7 @@ package ink.trmnl.android.data
import com.slack.eithernet.successOrNull
import com.squareup.anvil.annotations.optional.SingleIn
import ink.trmnl.android.data.DevConfig.FAKE_API_RESPONSE
import ink.trmnl.android.BuildConfig.USE_FAKE_API
import ink.trmnl.android.di.AppScope
import ink.trmnl.android.network.TrmnlApiService
import ink.trmnl.android.util.HTTP_200
@@ -14,11 +14,11 @@ import javax.inject.Inject
/**
* Repository class responsible for fetching and mapping display data.
*
* ⚠️ NOTE: [FAKE_API_RESPONSE] is set to `true` in debug builds, meaning it will
* ⚠️ NOTE: [USE_FAKE_API] is set to `true` in debug builds, meaning it will
* use mock data and avoid network calls. In release builds, it is set to `false`
* to enable real API calls.
*
* You can override this behavior by updating [DevConfig.FAKE_API_RESPONSE] for local development.
* You can override this behavior by updating [RepositoryConfigProvider.shouldUseFakeData] for local development.
*/
@SingleIn(AppScope::class)
class TrmnlDisplayRepository
@@ -118,7 +118,7 @@ class TrmnlDisplayRepository
/**
* Generates fake display info for debugging purposes without wasting an API request.
*
* ️ This is only used when [FAKE_API_RESPONSE] is set to `true`.
* ️ This is only used when [RepositoryConfigProvider.shouldUseFakeData] is returns `true`.
*/
private suspend fun fakeTrmnlDisplayInfo(apiUsed: String): TrmnlDisplayInfo {
Timber.d("DEBUG: Using mock data for display info")
@@ -8,6 +8,9 @@ import timber.log.Timber
import java.io.InputStream
import java.io.OutputStream
/**
* Serializer for [TrmnlRefreshLogs] using Moshi.
*/
object TrmnlRefreshLogSerializer : Serializer<TrmnlRefreshLogs> {
private val moshi = Moshi.Builder().build()
private val adapter = moshi.adapter(TrmnlRefreshLogs::class.java)
@@ -27,12 +30,12 @@ object TrmnlRefreshLogSerializer : Serializer<TrmnlRefreshLogs> {
}
override suspend fun writeTo(
t: TrmnlRefreshLogs,
refreshLogs: TrmnlRefreshLogs,
output: OutputStream,
) {
withContext(Dispatchers.IO) {
try {
val jsonString = adapter.toJson(t)
val jsonString = adapter.toJson(refreshLogs)
output.write(jsonString.toByteArray())
} catch (e: Exception) {
Timber.e(e, "Error writing activity logs")
@@ -20,7 +20,6 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Clear
import androidx.compose.material.icons.filled.DateRange
import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material.icons.outlined.Warning
import androidx.compose.material3.Button
@@ -82,7 +81,7 @@ import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import ink.trmnl.android.R
import ink.trmnl.android.data.AppConfig.DEFAULT_REFRESH_INTERVAL_SEC
import ink.trmnl.android.data.DevConfig
import ink.trmnl.android.data.RepositoryConfigProvider
import ink.trmnl.android.data.TrmnlDisplayRepository
import ink.trmnl.android.data.TrmnlTokenDataStore
import ink.trmnl.android.di.AppScope
@@ -123,6 +122,7 @@ data class AppSettingsScreen(
) : Screen {
data class State(
val accessToken: String,
val usesFakeApiData: Boolean,
val isLoading: Boolean = false,
val validationResult: ValidationResult? = null,
val nextRefreshJobInfo: NextImageRefreshDisplayInfo? = null,
@@ -186,12 +186,14 @@ class AppSettingsPresenter
private val trmnlTokenDataStore: TrmnlTokenDataStore,
private val trmnlWorkScheduler: TrmnlWorkScheduler,
private val trmnlImageUpdateManager: TrmnlImageUpdateManager,
private val repositoryConfigProvider: RepositoryConfigProvider,
) : Presenter<AppSettingsScreen.State> {
@Composable
override fun present(): AppSettingsScreen.State {
var accessToken by remember { mutableStateOf("") }
var isLoading by remember { mutableStateOf(false) }
var validationResult by remember { mutableStateOf<ValidationResult?>(null) }
val usesFakeApiData by remember { mutableStateOf(repositoryConfigProvider.shouldUseFakeData) }
val scope = rememberCoroutineScope()
val focusManager = LocalFocusManager.current
@@ -212,6 +214,7 @@ class AppSettingsPresenter
return AppSettingsScreen.State(
accessToken = accessToken,
usesFakeApiData = usesFakeApiData,
isLoading = isLoading,
validationResult = validationResult,
nextRefreshJobInfo = nextRefreshInfo,
@@ -352,8 +355,7 @@ fun AppSettingsContent(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
if (DevConfig.FAKE_API_RESPONSE) {
// Show fake API banner when DevConfig.FAKE_API_RESPONSE is `true`
if (state.usesFakeApiData) {
FakeApiInfoBanner(
modifier =
Modifier
@@ -655,7 +657,7 @@ private fun FakeApiInfoBanner(modifier: Modifier = Modifier) {
)
Text(
text = "Set `DevConfig.FAKE_API_RESPONSE` to `false` to use real API.",
text = "Set `BuildConfig.USE_FAKE_API` to `false` using build.gradle to use real API.",
style = MaterialTheme.typography.bodySmall,
fontStyle = FontStyle.Italic,
)
@@ -727,6 +729,7 @@ private fun PreviewAppSettingsContentInitial() {
state =
AppSettingsScreen.State(
accessToken = "",
usesFakeApiData = true,
isLoading = false,
validationResult = null,
nextRefreshJobInfo = null,
@@ -744,6 +747,7 @@ private fun PreviewAppSettingsContentLoading() {
state =
AppSettingsScreen.State(
accessToken = "some-token",
usesFakeApiData = false,
isLoading = true,
validationResult = null,
nextRefreshJobInfo = null,
@@ -761,6 +765,7 @@ private fun PreviewAppSettingsContentSuccess() {
state =
AppSettingsScreen.State(
accessToken = "valid-token-123",
usesFakeApiData = false,
isLoading = false,
validationResult =
ValidationResult.Success(
@@ -782,6 +787,7 @@ private fun PreviewAppSettingsContentFailure() {
state =
AppSettingsScreen.State(
accessToken = "invalid-token",
usesFakeApiData = false,
isLoading = false,
validationResult =
ValidationResult.Failure(
@@ -806,6 +812,7 @@ private fun PreviewAppSettingsContentWithWork() {
state =
AppSettingsScreen.State(
accessToken = "valid-token-123",
usesFakeApiData = false,
isLoading = false,
validationResult = null, // Can also be Success state
nextRefreshJobInfo =
@@ -833,6 +840,7 @@ private fun PreviewWorkScheduleStatusCardScheduled() {
state =
AppSettingsScreen.State(
accessToken = "some-token",
usesFakeApiData = false,
nextRefreshJobInfo =
NextImageRefreshDisplayInfo(
workerState = WorkInfo.State.ENQUEUED,
@@ -854,6 +862,7 @@ private fun PreviewWorkScheduleStatusCardNoWork() {
state =
AppSettingsScreen.State(
accessToken = "some-token",
usesFakeApiData = false,
nextRefreshJobInfo = null,
eventSink = {},
),
@@ -1,12 +0,0 @@
package ink.trmnl.android.data
/**
* Development configuration for the TRMNL app.
*/
object DevConfig {
/**
* Fake API response for local development and testing purposes.
* In release builds, we use real API responses.
*/
const val FAKE_API_RESPONSE = false
}