From 6cec3eb0c92bcbf18212aa7a12e058a0937694a3 Mon Sep 17 00:00:00 2001 From: Alexander Junggeburth Date: Fri, 13 May 2022 15:40:00 +0200 Subject: [PATCH 1/9] add support for androidx.startup initialization --- core/src/main/AndroidManifest.xml | 11 +++++ core/src/main/java/io/snabble/sdk/Config.kt | 37 ++++++++++++++- core/src/main/java/io/snabble/sdk/Snabble.kt | 41 ++++++++++------ .../java/io/snabble/sdk/SnabbleInitializer.kt | 47 +++++++++++++++++++ java-sample/src/main/AndroidManifest.xml | 8 ++++ .../src/main/java/io/snabble/testapp/App.java | 10 ---- .../sdk/ui/CheckoutContinuationInitializer.kt | 6 ++- .../sdk/ui/checkout/CheckoutActivity.kt | 1 + 8 files changed, 133 insertions(+), 28 deletions(-) create mode 100644 core/src/main/java/io/snabble/sdk/SnabbleInitializer.kt diff --git a/core/src/main/AndroidManifest.xml b/core/src/main/AndroidManifest.xml index f0df79092f..fa4a3f9229 100644 --- a/core/src/main/AndroidManifest.xml +++ b/core/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ @@ -7,6 +8,16 @@ + + + + + diff --git a/core/src/main/java/io/snabble/sdk/Config.kt b/core/src/main/java/io/snabble/sdk/Config.kt index 8e47a314b2..2baeec6435 100644 --- a/core/src/main/java/io/snabble/sdk/Config.kt +++ b/core/src/main/java/io/snabble/sdk/Config.kt @@ -1,6 +1,12 @@ package io.snabble.sdk +import android.content.Context +import io.snabble.sdk.utils.Dispatch +import io.snabble.sdk.utils.GsonHolder +import io.snabble.sdk.utils.Logger import okhttp3.Interceptor +import java.io.File +import java.lang.Exception import java.util.concurrent.TimeUnit data class Config ( @@ -135,4 +141,33 @@ data class Config ( */ @JvmField var manualProductDatabaseUpdates: Boolean = false, -) \ No newline at end of file +) { + fun save(context: Context) { + val file = File(context.filesDir, "snabble/${fileName}/") + val json = GsonHolder.get().toJson(this) + + Dispatch.io { + try { + file.writeText(json) + } catch (e: Exception) { + Logger.e("write exception [${file.path}]: $e") + } + } + } + + companion object { + val fileName = "config.json" + + fun restore(context: Context): Config? { + val file = File(context.filesDir, "snabble/${fileName}/") + return try { + val text = file.readText() + val config = GsonHolder.get().fromJson(text, Config::class.java) + config + } catch (e: Exception) { + Logger.e("read exception [${file.path}]: $e") + null + } + } + } +} \ No newline at end of file diff --git a/core/src/main/java/io/snabble/sdk/Snabble.kt b/core/src/main/java/io/snabble/sdk/Snabble.kt index ea9060991d..e574c0e398 100644 --- a/core/src/main/java/io/snabble/sdk/Snabble.kt +++ b/core/src/main/java/io/snabble/sdk/Snabble.kt @@ -297,8 +297,8 @@ object Snabble { * @param setupCompletionListener Completion listener that gets called when the SDK is ready. */ @JvmOverloads - fun setup(app: Application, config: Config, setupCompletionListener: SetupCompletionListener? = null) { - if (isInitializing.get()) { + fun setup(app: Application, config: Config?, setupCompletionListener: SetupCompletionListener? = null) { + if (isInitializing.get() || initializationState.value == InitializationState.INITIALIZED) { return } @@ -306,18 +306,29 @@ object Snabble { mutableInitializationState.postValue(InitializationState.INITIALIZING) application = app - this.config = config + if (config == null) { + val restoredConfig = Config.restore(app) + if (restoredConfig == null) { + setupCompletionListener?.onError(Error.CONFIG_ERROR) + return + } else { + this.config = restoredConfig + } + } else { + this.config = config + config.save(app) + } Logger.setErrorEventHandler { message, args -> Events.logErrorEvent(null, message, *args) } Logger.setLogEventHandler { message, args -> Events.logErrorEvent(null, message, *args) } - if (!config.endpointBaseUrl.startsWith("http://") - && !config.endpointBaseUrl.startsWith("https://")) { + if (!this.config.endpointBaseUrl.startsWith("http://") + && !this.config.endpointBaseUrl.startsWith("https://")) { setupCompletionListener?.onError(Error.CONFIG_ERROR) return } - var version = config.versionName + var version = this.config.versionName if (version == null) { version = try { val pInfo = app.packageManager.getPackageInfo(app.packageName, 0) @@ -328,31 +339,31 @@ object Snabble { } versionName = version - internalStorageDirectory = File(application.filesDir, "snabble/${config.appId}/") + internalStorageDirectory = File(application.filesDir, "snabble/${this.config.appId}/") internalStorageDirectory.mkdirs() okHttpClient = OkHttpClientFactory.createOkHttpClient(app) userPreferences = UserPreferences(app) - tokenRegistry = TokenRegistry(okHttpClient, userPreferences, config.appId, config.secret) + tokenRegistry = TokenRegistry(okHttpClient, userPreferences, this.config.appId, this.config.secret) receipts = Receipts() users = Users(userPreferences) brands = Collections.unmodifiableMap(emptyMap()) projects = Collections.unmodifiableList(emptyList()) - environment = Environment.getEnvironmentByUrl(config.endpointBaseUrl) - metadataUrl = absoluteUrl("/metadata/app/" + config.appId + "/android/" + version) + environment = Environment.getEnvironmentByUrl(this.config.endpointBaseUrl) + metadataUrl = absoluteUrl("/metadata/app/" + this.config.appId + "/android/" + version) paymentCredentialsStore = PaymentCredentialsStore() checkInLocationManager = CheckInLocationManager(application) checkInManager = CheckInManager(this, checkInLocationManager, - config.checkInRadius, - config.checkOutRadius, - config.lastSeenThreshold + this.config.checkInRadius, + this.config.checkOutRadius, + this.config.lastSeenThreshold ) - metadataDownloader = MetadataDownloader(okHttpClient, config.bundledMetadataAssetPath) + metadataDownloader = MetadataDownloader(okHttpClient, this.config.bundledMetadataAssetPath) - if (config.bundledMetadataAssetPath != null) { + if (this.config.bundledMetadataAssetPath != null) { dispatchOnReady(setupCompletionListener) } else { metadataDownloader.loadAsync(object : Downloader.Callback() { diff --git a/core/src/main/java/io/snabble/sdk/SnabbleInitializer.kt b/core/src/main/java/io/snabble/sdk/SnabbleInitializer.kt new file mode 100644 index 0000000000..a6138a1279 --- /dev/null +++ b/core/src/main/java/io/snabble/sdk/SnabbleInitializer.kt @@ -0,0 +1,47 @@ +package io.snabble.sdk + +import android.app.Application +import android.content.Context +import android.content.pm.PackageManager +import androidx.startup.Initializer + +/** + * Initializer for the snabble SDK using androidx.startup. + */ +class SnabbleInitializer : Initializer { + override fun create(context: Context): Snabble { + val app = context.applicationContext as Application + val applicationInfo = app.packageManager.getApplicationInfo(app.packageName, PackageManager.GET_META_DATA) + with(applicationInfo.metaData) { + if (getBoolean("snabble_auto_initialization_disabled")) { + return Snabble + } + + val config = Config().apply { + appId = getString("snabble_app_id", appId) + endpointBaseUrl = getString("snabble_endpoint_baseurl", endpointBaseUrl) + secret = getString("snabble_secret", secret) + bundledMetadataAssetPath = getString("snabble_bundled_metadata_asset_path", bundledMetadataAssetPath) + versionName = getString("snabble_version_name", versionName) + generateSearchIndex = getBoolean("snabble_generate_search_index", generateSearchIndex) + maxProductDatabaseAge = getLong("snabble_max_product_database_age", maxProductDatabaseAge) + maxShoppingCartAge = getLong("snabble_max_shopping_cart_age", maxShoppingCartAge) + disableCertificatePinning = getBoolean("snabble_disable_certificate_pinning") + initialSQL = getStringArrayList("snabble_initial_sql") ?: initialSQL + vibrateToConfirmCartFilled = getBoolean("snabble_vibrate_to_confirm_cart_filled", vibrateToConfirmCartFilled) + loadActiveShops = getBoolean("snabble_load_active_shops", loadActiveShops) + checkInRadius = getFloat("snabble_check_in_radius", checkInRadius) + checkOutRadius = getFloat("snabble_check_out_radius", checkOutRadius) + lastSeenThreshold = getLong("snabble_last_seen_threshold", lastSeenThreshold) + manualProductDatabaseUpdates = getBoolean("snabble_manual_product_database_updates", manualProductDatabaseUpdates) + } + + Snabble.setup(app, config, null) + return Snabble + } + } + + override fun dependencies(): List>> { + return emptyList() + } +} \ No newline at end of file diff --git a/java-sample/src/main/AndroidManifest.xml b/java-sample/src/main/AndroidManifest.xml index 0d632d8dc5..80a2d5c85a 100644 --- a/java-sample/src/main/AndroidManifest.xml +++ b/java-sample/src/main/AndroidManifest.xml @@ -27,5 +27,13 @@ + + + + \ No newline at end of file diff --git a/java-sample/src/main/java/io/snabble/testapp/App.java b/java-sample/src/main/java/io/snabble/testapp/App.java index 985fe53613..7c1c4275a7 100644 --- a/java-sample/src/main/java/io/snabble/testapp/App.java +++ b/java-sample/src/main/java/io/snabble/testapp/App.java @@ -32,16 +32,6 @@ public void onCreate() { //you may enable debug logging to see requests made by the sdk, and other various logs Snabble.setDebugLoggingEnabled(true); - // config { - Config config = new Config(); - config.endpointBaseUrl = getString(R.string.endpoint); - config.secret = getString(R.string.secret); - config.appId = getString(R.string.app_id); - // } - - final Snabble snabble = Snabble.getInstance(); - snabble.setup(this, config); - // sets a ui event listener for telemetry events, which can you redirect to any // telemetry provider Telemetry.setOnEventListener((event, data) -> { diff --git a/ui/src/main/java/io/snabble/sdk/ui/CheckoutContinuationInitializer.kt b/ui/src/main/java/io/snabble/sdk/ui/CheckoutContinuationInitializer.kt index 75348e2891..4a4d8b0133 100644 --- a/ui/src/main/java/io/snabble/sdk/ui/CheckoutContinuationInitializer.kt +++ b/ui/src/main/java/io/snabble/sdk/ui/CheckoutContinuationInitializer.kt @@ -5,13 +5,15 @@ import android.app.Application import android.content.Context import android.os.Bundle import androidx.startup.Initializer +import io.snabble.sdk.Snabble +import io.snabble.sdk.SnabbleInitializer import io.snabble.sdk.ui.checkout.CheckoutActivity import io.snabble.sdk.utils.Logger class SnabbleUIInitializerDummy /** - * Initializer for Snabble UI components using androidx.startup. + * Initializer for the snabble checkout using androidx.startup. */ class CheckoutContinuationInitializer : Initializer { override fun create(context: Context): SnabbleUIInitializerDummy { @@ -36,6 +38,6 @@ class CheckoutContinuationInitializer : Initializer { } override fun dependencies(): List>> { - return emptyList() + return listOf(SnabbleInitializer::class.java) } } \ No newline at end of file diff --git a/ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutActivity.kt b/ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutActivity.kt index 0cebdb3d71..f4378615c1 100644 --- a/ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutActivity.kt +++ b/ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutActivity.kt @@ -54,6 +54,7 @@ class CheckoutActivity : FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + Snabble.setup(application, null, null) Snabble.initializationState.observe(this) { when (it) { InitializationState.INITIALIZED -> { From 3421c7f4bb4c545ae06dda9b8f0a87866902279f Mon Sep 17 00:00:00 2001 From: Alexander Junggeburth Date: Fri, 13 May 2022 15:53:52 +0200 Subject: [PATCH 2/9] simplify sample app initialization --- java-sample/build.gradle | 18 ----- kotlin-customization-sample/build.gradle | 18 ----- .../src/main/AndroidManifest.xml | 8 ++ .../sdk/customization/LoadingActivity.kt | 81 ++++++++----------- kotlin-sample/build.gradle | 18 ----- kotlin-sample/src/main/AndroidManifest.xml | 8 ++ .../io/snabble/sdk/sample/LoadingActivity.kt | 79 ++++++++---------- 7 files changed, 85 insertions(+), 145 deletions(-) diff --git a/java-sample/build.gradle b/java-sample/build.gradle index 17867705ef..28cd49468f 100644 --- a/java-sample/build.gradle +++ b/java-sample/build.gradle @@ -11,24 +11,6 @@ android { versionCode 2 versionName '1.0' - // Setup here or in your local.properties file you Snabble secrets - def appId = null - def endpoint = null - def secret = null - - def configFile = project.rootProject.file('local.properties') - if ((appId == null || endpoint == null || secret == null) && configFile.exists()) { - Properties properties = new Properties() - properties.load(configFile.newDataInputStream()) - - appId = properties.getProperty('snabble.appId') - endpoint = properties.getProperty('snabble.endpoint') - secret = properties.getProperty('snabble.secret') - } - resValue 'string', 'app_id', appId ?: '' - resValue 'string', 'endpoint', endpoint ?: '' - resValue 'string', 'secret', secret ?: '' - testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } diff --git a/kotlin-customization-sample/build.gradle b/kotlin-customization-sample/build.gradle index 9774f32ba6..0374bc4828 100644 --- a/kotlin-customization-sample/build.gradle +++ b/kotlin-customization-sample/build.gradle @@ -11,24 +11,6 @@ android { versionCode 1 versionName '1.0' - // Setup here or in your local.properties file you Snabble secrets - def appId = null - def endpoint = null - def secret = null - - def configFile = project.rootProject.file('local.properties') - if ((appId == null || endpoint == null || secret == null) && configFile.exists()) { - Properties properties = new Properties() - properties.load(configFile.newDataInputStream()) - - appId = properties.getProperty('snabble.appId') - endpoint = properties.getProperty('snabble.endpoint') - secret = properties.getProperty('snabble.secret') - } - resValue 'string', 'app_id', appId ?: '' - resValue 'string', 'endpoint', endpoint ?: '' - resValue 'string', 'secret', secret ?: '' - testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } diff --git a/kotlin-customization-sample/src/main/AndroidManifest.xml b/kotlin-customization-sample/src/main/AndroidManifest.xml index 4fdb31fe51..fef8491431 100644 --- a/kotlin-customization-sample/src/main/AndroidManifest.xml +++ b/kotlin-customization-sample/src/main/AndroidManifest.xml @@ -34,5 +34,13 @@ + + + + \ No newline at end of file diff --git a/kotlin-customization-sample/src/main/java/io/snabble/sdk/customization/LoadingActivity.kt b/kotlin-customization-sample/src/main/java/io/snabble/sdk/customization/LoadingActivity.kt index 6f83825804..a93643d0e0 100644 --- a/kotlin-customization-sample/src/main/java/io/snabble/sdk/customization/LoadingActivity.kt +++ b/kotlin-customization-sample/src/main/java/io/snabble/sdk/customization/LoadingActivity.kt @@ -4,9 +4,8 @@ import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity -import io.snabble.sdk.Config +import io.snabble.sdk.InitializationState import io.snabble.sdk.Snabble -import io.snabble.sdk.ui.SnabbleUI class LoadingActivity : AppCompatActivity() { @@ -16,57 +15,45 @@ class LoadingActivity : AppCompatActivity() { } fun initSdk() { - // config { - val config = Config( - endpointBaseUrl = getString(R.string.endpoint), - appId = getString(R.string.app_id), - secret = getString(R.string.secret), - ) - //} - Snabble.setDebugLoggingEnabled(BuildConfig.DEBUG) + Snabble.initializationState.observe(this) { + when (it) { + InitializationState.INITIALIZING -> {} + InitializationState.INITIALIZED -> { + // an application can have multiple projects, for example for + // multiple independent regions / countries + val project = Snabble.projects.first() + + // check in to the first shop - you can use CheckInManager if you want + // to use geofencing + Snabble.checkedInShop = project.shops.first() + + // this is done on the background and can be done at any time + // a fully downloaded product database allows for scanning products while + // being offline + // + // if the product database is still downloading or you did not call update() + // online request will be used in the mean time + project.productDatabase.update() - Snabble.setup(application, config, object : Snabble.SetupCompletionListener { - override fun onReady() { - // an application can have multiple projects, for example for - // multiple independent regions / countries - val project = Snabble.projects.first() - - // register the project to our UI components - Snabble.checkedInProject.value = project - - // check in to the first shop - you can use CheckInManager if you want - // to use geofencing - Snabble.checkedInShop = project.shops.first() - - // this is done on the background and can be done at any time - // a fully downloaded product database allows for scanning products while - // being offline - // - // if the product database is still downloading or you did not call update() - // online request will be used in the mean time - project.productDatabase.update() - - runOnUiThread { startActivity(Intent(this@LoadingActivity, MainActivity::class.java)) finish() } - } - - override fun onError(error: Snabble.Error?) { - runOnUiThread { - AlertDialog.Builder(this@LoadingActivity) - .setMessage(error?.name) - .setPositiveButton("Retry") { _, _ -> - initSdk() - } - .setNegativeButton("Exit") { _, _ -> - finish() - } - .create() - .show() + InitializationState.ERROR -> { + runOnUiThread { + AlertDialog.Builder(this@LoadingActivity) + .setMessage("SDK initialization error") + .setPositiveButton("Retry") { _, _ -> + initSdk() + } + .setNegativeButton("Exit") { _, _ -> + finish() + } + .create() + .show() + } } } - }) + } } } \ No newline at end of file diff --git a/kotlin-sample/build.gradle b/kotlin-sample/build.gradle index 962a042ae7..fd0f1cdda7 100644 --- a/kotlin-sample/build.gradle +++ b/kotlin-sample/build.gradle @@ -11,24 +11,6 @@ android { versionCode 1 versionName '1.0' - // Setup here or in your local.properties file you Snabble secrets - def appId = null - def endpoint = null - def secret = null - - def configFile = project.rootProject.file('local.properties') - if ((appId == null || endpoint == null || secret == null) && configFile.exists()) { - Properties properties = new Properties() - properties.load(configFile.newDataInputStream()) - - appId = properties.getProperty('snabble.appId') - endpoint = properties.getProperty('snabble.endpoint') - secret = properties.getProperty('snabble.secret') - } - resValue 'string', 'app_id', appId ?: '' - resValue 'string', 'endpoint', endpoint ?: '' - resValue 'string', 'secret', secret ?: '' - testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } diff --git a/kotlin-sample/src/main/AndroidManifest.xml b/kotlin-sample/src/main/AndroidManifest.xml index 2b7b230bd8..e44dd16451 100644 --- a/kotlin-sample/src/main/AndroidManifest.xml +++ b/kotlin-sample/src/main/AndroidManifest.xml @@ -31,5 +31,13 @@ android:windowSoftInputMode="adjustPan" android:label="@string/app_name" android:exported="true"/> + + + + \ No newline at end of file diff --git a/kotlin-sample/src/main/java/io/snabble/sdk/sample/LoadingActivity.kt b/kotlin-sample/src/main/java/io/snabble/sdk/sample/LoadingActivity.kt index d81efab943..a213a87420 100644 --- a/kotlin-sample/src/main/java/io/snabble/sdk/sample/LoadingActivity.kt +++ b/kotlin-sample/src/main/java/io/snabble/sdk/sample/LoadingActivity.kt @@ -4,9 +4,9 @@ import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity -import io.snabble.sdk.Config + +import io.snabble.sdk.InitializationState import io.snabble.sdk.Snabble -import io.snabble.sdk.ui.SnabbleUI class LoadingActivity : AppCompatActivity() { @@ -16,54 +16,45 @@ class LoadingActivity : AppCompatActivity() { } fun initSdk() { - // config { - val config = Config( - endpointBaseUrl = getString(R.string.endpoint), - appId = getString(R.string.app_id), - secret = getString(R.string.secret), - ) - //} - Snabble.setDebugLoggingEnabled(BuildConfig.DEBUG) + Snabble.initializationState.observe(this) { + when (it) { + InitializationState.INITIALIZING -> {} + InitializationState.INITIALIZED -> { + // an application can have multiple projects, for example for + // multiple independent regions / countries + val project = Snabble.projects.first() + + // check in to the first shop - you can use CheckInManager if you want + // to use geofencing + Snabble.checkedInShop = project.shops.first() + + // this is done on the background and can be done at any time + // a fully downloaded product database allows for scanning products while + // being offline + // + // if the product database is still downloading or you did not call update() + // online request will be used in the mean time + project.productDatabase.update() - Snabble.setup(application, config, object : Snabble.SetupCompletionListener { - override fun onReady() { - // an application can have multiple projects, for example for - // multiple independent regions / countries - val project = Snabble.projects.first() - - // check in to the first shop - you can use CheckInManager if you want - // to use geofencing - Snabble.checkedInShop = project.shops.first() - - // this is done on the background and can be done at any time - // a fully downloaded product database allows for scanning products while - // being offline - // - // if the product database is still downloading or you did not call update() - // online request will be used in the mean time - project.productDatabase.update() - - runOnUiThread { startActivity(Intent(this@LoadingActivity, MainActivity::class.java)) finish() } - } - - override fun onError(error: Snabble.Error?) { - runOnUiThread { - AlertDialog.Builder(this@LoadingActivity) - .setMessage(error?.name) - .setPositiveButton("Retry") { _, _ -> - initSdk() - } - .setNegativeButton("Exit") { _, _ -> - finish() - } - .create() - .show() + InitializationState.ERROR -> { + runOnUiThread { + AlertDialog.Builder(this@LoadingActivity) + .setMessage("SDK initialization error") + .setPositiveButton("Retry") { _, _ -> + initSdk() + } + .setNegativeButton("Exit") { _, _ -> + finish() + } + .create() + .show() + } } } - }) + } } } \ No newline at end of file From 0817d4cb7617a6d3d74c58699df0137597af498a Mon Sep 17 00:00:00 2001 From: Alexander Junggeburth Date: Fri, 13 May 2022 16:03:31 +0200 Subject: [PATCH 3/9] allow for initialization retries --- core/src/main/java/io/snabble/sdk/Snabble.kt | 2 +- .../main/java/io/snabble/sdk/customization/LoadingActivity.kt | 2 +- .../src/main/java/io/snabble/sdk/sample/LoadingActivity.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/io/snabble/sdk/Snabble.kt b/core/src/main/java/io/snabble/sdk/Snabble.kt index e574c0e398..5ab89d9838 100644 --- a/core/src/main/java/io/snabble/sdk/Snabble.kt +++ b/core/src/main/java/io/snabble/sdk/Snabble.kt @@ -324,7 +324,7 @@ object Snabble { if (!this.config.endpointBaseUrl.startsWith("http://") && !this.config.endpointBaseUrl.startsWith("https://")) { - setupCompletionListener?.onError(Error.CONFIG_ERROR) + dispatchError(setupCompletionListener) return } diff --git a/kotlin-customization-sample/src/main/java/io/snabble/sdk/customization/LoadingActivity.kt b/kotlin-customization-sample/src/main/java/io/snabble/sdk/customization/LoadingActivity.kt index a93643d0e0..8bd43f0271 100644 --- a/kotlin-customization-sample/src/main/java/io/snabble/sdk/customization/LoadingActivity.kt +++ b/kotlin-customization-sample/src/main/java/io/snabble/sdk/customization/LoadingActivity.kt @@ -44,7 +44,7 @@ class LoadingActivity : AppCompatActivity() { AlertDialog.Builder(this@LoadingActivity) .setMessage("SDK initialization error") .setPositiveButton("Retry") { _, _ -> - initSdk() + Snabble.setup(application, null, null) } .setNegativeButton("Exit") { _, _ -> finish() diff --git a/kotlin-sample/src/main/java/io/snabble/sdk/sample/LoadingActivity.kt b/kotlin-sample/src/main/java/io/snabble/sdk/sample/LoadingActivity.kt index a213a87420..3b12ba6c57 100644 --- a/kotlin-sample/src/main/java/io/snabble/sdk/sample/LoadingActivity.kt +++ b/kotlin-sample/src/main/java/io/snabble/sdk/sample/LoadingActivity.kt @@ -45,7 +45,7 @@ class LoadingActivity : AppCompatActivity() { AlertDialog.Builder(this@LoadingActivity) .setMessage("SDK initialization error") .setPositiveButton("Retry") { _, _ -> - initSdk() + Snabble.setup(application, null, null) } .setNegativeButton("Exit") { _, _ -> finish() From fd5fc239f6a67acac408dfe361422cac62998d27 Mon Sep 17 00:00:00 2001 From: Alexander Junggeburth Date: Fri, 13 May 2022 16:20:34 +0200 Subject: [PATCH 4/9] add snabble error property --- core/src/main/java/io/snabble/sdk/Snabble.kt | 61 ++++++++----------- .../java/io/snabble/sdk/SnabbleInitializer.kt | 2 +- .../sdk/customization/LoadingActivity.kt | 4 +- .../io/snabble/sdk/sample/LoadingActivity.kt | 4 +- .../sdk/ui/checkout/CheckoutActivity.kt | 2 +- 5 files changed, 32 insertions(+), 41 deletions(-) diff --git a/core/src/main/java/io/snabble/sdk/Snabble.kt b/core/src/main/java/io/snabble/sdk/Snabble.kt index 5ab89d9838..fad31c02b4 100644 --- a/core/src/main/java/io/snabble/sdk/Snabble.kt +++ b/core/src/main/java/io/snabble/sdk/Snabble.kt @@ -12,6 +12,7 @@ import android.net.NetworkCapabilities import android.net.NetworkRequest import android.util.Base64 import androidx.lifecycle.* +import androidx.lifecycle.Observer import com.google.gson.JsonObject import io.snabble.sdk.auth.TokenRegistry import io.snabble.sdk.checkin.CheckInLocationManager @@ -283,6 +284,12 @@ object Snabble { private val isInitializing = AtomicBoolean(false) + /** + * Get the error during initialization of the SDK occurred + */ + var error: Error? = null + private set + /** * Setup the snabble SDK. * @@ -294,10 +301,9 @@ object Snabble { * * @param app Your main android application * @param config Config provided. Minimal required fields are appId and secret. - * @param setupCompletionListener Completion listener that gets called when the SDK is ready. */ @JvmOverloads - fun setup(app: Application, config: Config?, setupCompletionListener: SetupCompletionListener? = null) { + fun setup(app: Application, config: Config? = null) { if (isInitializing.get() || initializationState.value == InitializationState.INITIALIZED) { return } @@ -309,7 +315,7 @@ object Snabble { if (config == null) { val restoredConfig = Config.restore(app) if (restoredConfig == null) { - setupCompletionListener?.onError(Error.CONFIG_ERROR) + dispatchError(Error.CONFIG_ERROR) return } else { this.config = restoredConfig @@ -324,7 +330,7 @@ object Snabble { if (!this.config.endpointBaseUrl.startsWith("http://") && !this.config.endpointBaseUrl.startsWith("https://")) { - dispatchError(setupCompletionListener) + dispatchError(Error.CONFIG_ERROR) return } @@ -364,18 +370,18 @@ object Snabble { metadataDownloader = MetadataDownloader(okHttpClient, this.config.bundledMetadataAssetPath) if (this.config.bundledMetadataAssetPath != null) { - dispatchOnReady(setupCompletionListener) + dispatchOnReady() } else { metadataDownloader.loadAsync(object : Downloader.Callback() { override fun onDataLoaded(wasStillValid: Boolean) { - dispatchOnReady(setupCompletionListener) + dispatchOnReady() } override fun onError() { if (metadataDownloader.hasData()) { - dispatchOnReady(setupCompletionListener) + dispatchOnReady() } else { - dispatchError(setupCompletionListener) + dispatchError(Error.CONNECTION_TIMEOUT) } } }) @@ -394,7 +400,7 @@ object Snabble { registerNetworkCallback(app) } - private fun dispatchOnReady(setupCompletionListener: SetupCompletionListener?) { + private fun dispatchOnReady() { Dispatch.background { readMetadata() val appUser = userPreferences.appUser @@ -403,32 +409,23 @@ object Snabble { if (token == null) { isInitializing.set(false) mutableInitializationState.postValue(InitializationState.ERROR) - - Dispatch.mainThread { - setupCompletionListener?.onError(Error.CONNECTION_TIMEOUT) - } return@background } } isInitializing.set(false) mutableInitializationState.postValue(InitializationState.INITIALIZED) - Dispatch.mainThread { - setupCompletionListener?.onReady() - } + if (config.loadActiveShops) { loadActiveShops() } } } - private fun dispatchError(setupCompletionListener: SetupCompletionListener?) { + private fun dispatchError(error: Error) { isInitializing.set(false) mutableInitializationState.postValue(InitializationState.ERROR) - - Dispatch.mainThread { - setupCompletionListener?.onError(Error.CONNECTION_TIMEOUT) - } + this.error = error } /** @@ -443,19 +440,18 @@ object Snabble { * @throws SnabbleException If an error occurs while initializing the sdk. */ @Throws(SnabbleException::class) - fun setupBlocking(app: Application, config: Config) { + @JvmOverloads + fun setupBlocking(app: Application, config: Config? = null) { val countDownLatch = CountDownLatch(1) val snabbleError = arrayOfNulls(1) - setup(app, config, object : SetupCompletionListener { - override fun onReady() { - countDownLatch.countDown() - } - - override fun onError(error: Error?) { - snabbleError[0] = error + setup(app, config) + val observer = object : Observer { + override fun onChanged(t: InitializationState?) { countDownLatch.countDown() + initializationState.removeObserver(this) } - }) + } + initializationState.observeForever(observer) try { countDownLatch.await() } catch (e: InterruptedException) { @@ -726,11 +722,6 @@ object Snabble { } } - interface SetupCompletionListener { - fun onReady() - fun onError(error: Error?) - } - class SnabbleException internal constructor(val error: Error?) : Exception() { override fun toString(): String { return "SnabbleException{" + diff --git a/core/src/main/java/io/snabble/sdk/SnabbleInitializer.kt b/core/src/main/java/io/snabble/sdk/SnabbleInitializer.kt index a6138a1279..bf0d23b70b 100644 --- a/core/src/main/java/io/snabble/sdk/SnabbleInitializer.kt +++ b/core/src/main/java/io/snabble/sdk/SnabbleInitializer.kt @@ -36,7 +36,7 @@ class SnabbleInitializer : Initializer { manualProductDatabaseUpdates = getBoolean("snabble_manual_product_database_updates", manualProductDatabaseUpdates) } - Snabble.setup(app, config, null) + Snabble.setup(app, config) return Snabble } } diff --git a/kotlin-customization-sample/src/main/java/io/snabble/sdk/customization/LoadingActivity.kt b/kotlin-customization-sample/src/main/java/io/snabble/sdk/customization/LoadingActivity.kt index 8bd43f0271..b2ad78db04 100644 --- a/kotlin-customization-sample/src/main/java/io/snabble/sdk/customization/LoadingActivity.kt +++ b/kotlin-customization-sample/src/main/java/io/snabble/sdk/customization/LoadingActivity.kt @@ -42,9 +42,9 @@ class LoadingActivity : AppCompatActivity() { InitializationState.ERROR -> { runOnUiThread { AlertDialog.Builder(this@LoadingActivity) - .setMessage("SDK initialization error") + .setMessage(Snabble.error?.name) .setPositiveButton("Retry") { _, _ -> - Snabble.setup(application, null, null) + Snabble.setup(application) } .setNegativeButton("Exit") { _, _ -> finish() diff --git a/kotlin-sample/src/main/java/io/snabble/sdk/sample/LoadingActivity.kt b/kotlin-sample/src/main/java/io/snabble/sdk/sample/LoadingActivity.kt index 3b12ba6c57..de62996fb9 100644 --- a/kotlin-sample/src/main/java/io/snabble/sdk/sample/LoadingActivity.kt +++ b/kotlin-sample/src/main/java/io/snabble/sdk/sample/LoadingActivity.kt @@ -43,9 +43,9 @@ class LoadingActivity : AppCompatActivity() { InitializationState.ERROR -> { runOnUiThread { AlertDialog.Builder(this@LoadingActivity) - .setMessage("SDK initialization error") + .setMessage(Snabble.error?.name) .setPositiveButton("Retry") { _, _ -> - Snabble.setup(application, null, null) + Snabble.setup(application) } .setNegativeButton("Exit") { _, _ -> finish() diff --git a/ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutActivity.kt b/ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutActivity.kt index f4378615c1..cebe8660ea 100644 --- a/ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutActivity.kt +++ b/ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutActivity.kt @@ -54,7 +54,7 @@ class CheckoutActivity : FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - Snabble.setup(application, null, null) + Snabble.setup(application) Snabble.initializationState.observe(this) { when (it) { InitializationState.INITIALIZED -> { From 69b9fe9ca39e9083c396b51c766af64d6801f4d4 Mon Sep 17 00:00:00 2001 From: Alexander Junggeburth Date: Fri, 13 May 2022 16:35:27 +0200 Subject: [PATCH 5/9] update docs --- core/src/main/java/io/snabble/sdk/Snabble.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/io/snabble/sdk/Snabble.kt b/core/src/main/java/io/snabble/sdk/Snabble.kt index fad31c02b4..2e66d2151c 100644 --- a/core/src/main/java/io/snabble/sdk/Snabble.kt +++ b/core/src/main/java/io/snabble/sdk/Snabble.kt @@ -300,7 +300,11 @@ object Snabble { * raw metadata from our backend to bundledMetadataAssetPath in the assets folder. * * @param app Your main android application - * @param config Config provided. Minimal required fields are appId and secret. + * @param config Config provided. Minimal required fields are appId and secret. If no config + * is provided and the sdk was initialized before, a serialized version will be used. + * + * If this is the first time you initialize the snabble SDK, a config must be set + * or the initialization will fail with Error.CONFIG_ERROR. */ @JvmOverloads fun setup(app: Application, config: Config? = null) { From 3c3f1c3c780bbe48ec7084ccb6783c85551d9362 Mon Sep 17 00:00:00 2001 From: Alexander Junggeburth Date: Mon, 16 May 2022 16:36:13 +0200 Subject: [PATCH 6/9] Allow serialization and deserialization of Interceptors in Snabble.Config --- core/src/main/java/io/snabble/sdk/Config.kt | 41 +++++++++++++++++-- core/src/main/java/io/snabble/sdk/Project.kt | 4 +- core/src/main/java/io/snabble/sdk/Snabble.kt | 27 ++++++++---- .../java/io/snabble/sdk/SnabbleInitializer.kt | 13 ++++++ .../src/main/AndroidManifest.xml | 1 - .../sdk/customization/LoadingActivity.kt | 2 +- kotlin-sample/src/main/AndroidManifest.xml | 6 ++- .../io/snabble/sdk/sample/LoadingActivity.kt | 2 +- .../snabble/sdk/sample/LoggingInterceptor.kt | 13 ++++++ .../ui/checkout/CheckoutCustomerCardView.java | 6 +-- .../sdk/ui/checkout/CheckoutOfflineView.java | 4 +- .../ui/checkout/CheckoutPointOfSaleView.java | 13 ++---- .../java/io/snabble/sdk/ui/utils/ViewExt.kt | 1 + 13 files changed, 97 insertions(+), 36 deletions(-) create mode 100644 kotlin-sample/src/main/java/io/snabble/sdk/sample/LoggingInterceptor.kt diff --git a/core/src/main/java/io/snabble/sdk/Config.kt b/core/src/main/java/io/snabble/sdk/Config.kt index 2baeec6435..9a5f07cccc 100644 --- a/core/src/main/java/io/snabble/sdk/Config.kt +++ b/core/src/main/java/io/snabble/sdk/Config.kt @@ -1,12 +1,14 @@ package io.snabble.sdk import android.content.Context +import com.google.gson.* import io.snabble.sdk.utils.Dispatch import io.snabble.sdk.utils.GsonHolder import io.snabble.sdk.utils.Logger import okhttp3.Interceptor import java.io.File import java.lang.Exception +import java.lang.reflect.Type import java.util.concurrent.TimeUnit data class Config ( @@ -144,8 +146,7 @@ data class Config ( ) { fun save(context: Context) { val file = File(context.filesDir, "snabble/${fileName}/") - val json = GsonHolder.get().toJson(this) - + val json = gson.toJson(this) Dispatch.io { try { file.writeText(json) @@ -156,13 +157,18 @@ data class Config ( } companion object { - val fileName = "config.json" + const val fileName = "config.json" + + private val gson = GsonHolder.get() + .newBuilder() + .registerTypeAdapter(Interceptor::class.java, InterceptorSerializer) + .create() fun restore(context: Context): Config? { val file = File(context.filesDir, "snabble/${fileName}/") return try { val text = file.readText() - val config = GsonHolder.get().fromJson(text, Config::class.java) + val config = gson.fromJson(text, Config::class.java) config } catch (e: Exception) { Logger.e("read exception [${file.path}]: $e") @@ -170,4 +176,31 @@ data class Config ( } } } +} + +object InterceptorSerializer : JsonSerializer, JsonDeserializer { + override fun serialize(src: Interceptor?, + typeOfSrc: Type?, + context: JsonSerializationContext? + ): JsonElement { + val cls = src?.javaClass?.name + return if (cls != null) { + JsonPrimitive(cls) + } else { + JsonNull.INSTANCE + } + } + + override fun deserialize(json: JsonElement?, + typeOfT: Type?, + context: JsonDeserializationContext? + ): Interceptor? { + val cls = json?.asString + return if (cls != null) { + val clazz = Class.forName(cls) + clazz.newInstance() as Interceptor + } else { + null + } + } } \ No newline at end of file diff --git a/core/src/main/java/io/snabble/sdk/Project.kt b/core/src/main/java/io/snabble/sdk/Project.kt index 0a5ba1a57c..4aaebc6da3 100644 --- a/core/src/main/java/io/snabble/sdk/Project.kt +++ b/core/src/main/java/io/snabble/sdk/Project.kt @@ -502,7 +502,7 @@ class Project internal constructor(jsonObject: JsonObject) { .map { it.paymentMethod } .firstOrNull { it == PaymentMethod.GOOGLE_PAY } ?.let { - GooglePayHelper(this, instance.application) + GooglePayHelper(this, Snabble.application) } coupons = Coupons(this) @@ -518,7 +518,7 @@ class Project internal constructor(jsonObject: JsonObject) { .map { it.paymentMethod } .firstOrNull { it == PaymentMethod.GOOGLE_PAY } ?.let { - GooglePayHelper(this, instance.application) + GooglePayHelper(this, Snabble.application) } /** diff --git a/core/src/main/java/io/snabble/sdk/Snabble.kt b/core/src/main/java/io/snabble/sdk/Snabble.kt index 2e66d2151c..e258ffb091 100644 --- a/core/src/main/java/io/snabble/sdk/Snabble.kt +++ b/core/src/main/java/io/snabble/sdk/Snabble.kt @@ -307,7 +307,7 @@ object Snabble { * or the initialization will fail with Error.CONFIG_ERROR. */ @JvmOverloads - fun setup(app: Application, config: Config? = null) { + fun setup(app: Application? = null, config: Config? = null) { if (isInitializing.get() || initializationState.value == InitializationState.INITIALIZED) { return } @@ -315,9 +315,17 @@ object Snabble { isInitializing.set(true) mutableInitializationState.postValue(InitializationState.INITIALIZING) - application = app + if (app != null) { + application = app + } + + if (!this::application.isInitialized) { + dispatchError(Error.NO_APPLICATION_SET) + return + } + if (config == null) { - val restoredConfig = Config.restore(app) + val restoredConfig = Config.restore(application) if (restoredConfig == null) { dispatchError(Error.CONFIG_ERROR) return @@ -326,7 +334,7 @@ object Snabble { } } else { this.config = config - config.save(app) + config.save(application) } Logger.setErrorEventHandler { message, args -> Events.logErrorEvent(null, message, *args) } @@ -341,7 +349,7 @@ object Snabble { var version = this.config.versionName if (version == null) { version = try { - val pInfo = app.packageManager.getPackageInfo(app.packageName, 0) + val pInfo = application.packageManager.getPackageInfo(application.packageName, 0) pInfo?.versionName?.lowercase(Locale.ROOT)?.replace(" ", "") ?: "1.0" } catch (e: PackageManager.NameNotFoundException) { "1.0" @@ -352,8 +360,8 @@ object Snabble { internalStorageDirectory = File(application.filesDir, "snabble/${this.config.appId}/") internalStorageDirectory.mkdirs() - okHttpClient = OkHttpClientFactory.createOkHttpClient(app) - userPreferences = UserPreferences(app) + okHttpClient = OkHttpClientFactory.createOkHttpClient(application) + userPreferences = UserPreferences(application) tokenRegistry = TokenRegistry(okHttpClient, userPreferences, this.config.appId, this.config.secret) receipts = Receipts() users = Users(userPreferences) @@ -391,7 +399,7 @@ object Snabble { }) } - app.registerActivityLifecycleCallbacks(activityLifecycleCallbacks) + application.registerActivityLifecycleCallbacks(activityLifecycleCallbacks) ProcessLifecycleOwner.get().lifecycle.addObserver(object : DefaultLifecycleObserver { override fun onStart(owner: LifecycleOwner) { @@ -401,7 +409,7 @@ object Snabble { } }) - registerNetworkCallback(app) + registerNetworkCallback(application) } private fun dispatchOnReady() { @@ -736,6 +744,7 @@ object Snabble { enum class Error { UNSPECIFIED_ERROR, + NO_APPLICATION_SET, CONFIG_ERROR, CONNECTION_TIMEOUT } diff --git a/core/src/main/java/io/snabble/sdk/SnabbleInitializer.kt b/core/src/main/java/io/snabble/sdk/SnabbleInitializer.kt index bf0d23b70b..365a3733ac 100644 --- a/core/src/main/java/io/snabble/sdk/SnabbleInitializer.kt +++ b/core/src/main/java/io/snabble/sdk/SnabbleInitializer.kt @@ -4,6 +4,8 @@ import android.app.Application import android.content.Context import android.content.pm.PackageManager import androidx.startup.Initializer +import io.snabble.sdk.utils.Logger +import okhttp3.Interceptor /** * Initializer for the snabble SDK using androidx.startup. @@ -33,9 +35,20 @@ class SnabbleInitializer : Initializer { checkInRadius = getFloat("snabble_check_in_radius", checkInRadius) checkOutRadius = getFloat("snabble_check_out_radius", checkOutRadius) lastSeenThreshold = getLong("snabble_last_seen_threshold", lastSeenThreshold) + networkInterceptor = + try { + Class.forName(getString("snabble_network_interceptor", null)).newInstance() as Interceptor? + } catch (e: Exception) { + Logger.d("Could not instantiate network interceptor", e.message) + null + } manualProductDatabaseUpdates = getBoolean("snabble_manual_product_database_updates", manualProductDatabaseUpdates) } + if (config.appId == null || config.secret == null) { + throw IllegalArgumentException("You need to set 'snabble_app_id' and 'snabble_secret' in your AndroidManifest.xml") + } + Snabble.setup(app, config) return Snabble } diff --git a/kotlin-customization-sample/src/main/AndroidManifest.xml b/kotlin-customization-sample/src/main/AndroidManifest.xml index fef8491431..f059534e76 100644 --- a/kotlin-customization-sample/src/main/AndroidManifest.xml +++ b/kotlin-customization-sample/src/main/AndroidManifest.xml @@ -28,7 +28,6 @@ - Snabble.setup(application) + Snabble.setup() } .setNegativeButton("Exit") { _, _ -> finish() diff --git a/kotlin-sample/src/main/AndroidManifest.xml b/kotlin-sample/src/main/AndroidManifest.xml index e44dd16451..fa5f41d9dd 100644 --- a/kotlin-sample/src/main/AndroidManifest.xml +++ b/kotlin-sample/src/main/AndroidManifest.xml @@ -18,7 +18,6 @@ android:theme="@style/AppTheme"> @@ -29,7 +28,6 @@ + + \ No newline at end of file diff --git a/kotlin-sample/src/main/java/io/snabble/sdk/sample/LoadingActivity.kt b/kotlin-sample/src/main/java/io/snabble/sdk/sample/LoadingActivity.kt index de62996fb9..ff22186072 100644 --- a/kotlin-sample/src/main/java/io/snabble/sdk/sample/LoadingActivity.kt +++ b/kotlin-sample/src/main/java/io/snabble/sdk/sample/LoadingActivity.kt @@ -45,7 +45,7 @@ class LoadingActivity : AppCompatActivity() { AlertDialog.Builder(this@LoadingActivity) .setMessage(Snabble.error?.name) .setPositiveButton("Retry") { _, _ -> - Snabble.setup(application) + Snabble.setup() } .setNegativeButton("Exit") { _, _ -> finish() diff --git a/kotlin-sample/src/main/java/io/snabble/sdk/sample/LoggingInterceptor.kt b/kotlin-sample/src/main/java/io/snabble/sdk/sample/LoggingInterceptor.kt new file mode 100644 index 0000000000..4be4992e46 --- /dev/null +++ b/kotlin-sample/src/main/java/io/snabble/sdk/sample/LoggingInterceptor.kt @@ -0,0 +1,13 @@ +package io.snabble.sdk.sample + +import android.util.Log +import okhttp3.Interceptor +import okhttp3.Response + +class LoggingInterceptor : Interceptor { + override fun intercept(chain: Interceptor.Chain): Response { + val url = chain.call().request().url + Log.d("LoggingInterceptor", url.toString()) + return chain.proceed(chain.request()) + } +} \ No newline at end of file diff --git a/ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutCustomerCardView.java b/ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutCustomerCardView.java index 8a5352480c..026cd2c450 100644 --- a/ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutCustomerCardView.java +++ b/ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutCustomerCardView.java @@ -13,17 +13,15 @@ import android.widget.LinearLayout; import io.snabble.sdk.BarcodeFormat; -import io.snabble.sdk.checkout.Checkout; import io.snabble.sdk.Project; import io.snabble.sdk.Snabble; -import io.snabble.sdk.checkout.CheckoutState; import io.snabble.sdk.codes.EAN13; import io.snabble.sdk.ui.R; import io.snabble.sdk.ui.scanner.BarcodeView; import io.snabble.sdk.ui.telemetry.Telemetry; import io.snabble.sdk.ui.utils.I18nUtils; import io.snabble.sdk.ui.utils.OneShotClickListener; -import io.snabble.sdk.ui.utils.ViewExtKt; +import io.snabble.sdk.ui.utils.ViewUtils; public class CheckoutCustomerCardView extends FrameLayout { private Project project; @@ -51,7 +49,7 @@ private void init() { project = Snabble.getInstance().getCheckedInProject().getValue(); - ViewExtKt.observeView(Snabble.getInstance().getCheckedInProject(), this, p -> { + ViewUtils.observeView(Snabble.getInstance().getCheckedInProject(), this, p -> { project = p; update(); }); diff --git a/ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutOfflineView.java b/ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutOfflineView.java index 8f71231779..7e3c811951 100644 --- a/ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutOfflineView.java +++ b/ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutOfflineView.java @@ -32,7 +32,7 @@ import io.snabble.sdk.ui.telemetry.Telemetry; import io.snabble.sdk.ui.utils.I18nUtils; import io.snabble.sdk.ui.utils.OneShotClickListener; -import io.snabble.sdk.ui.utils.ViewExtKt; +import io.snabble.sdk.ui.utils.ViewUtils; import io.snabble.sdk.utils.Dispatch; import me.relex.circleindicator.CircleIndicator3; @@ -68,7 +68,7 @@ private void init() { project = Snabble.getInstance().getCheckedInProject().getValue(); - ViewExtKt.observeView(Snabble.getInstance().getCheckedInProject(), this, p -> { + ViewUtils.observeView(Snabble.getInstance().getCheckedInProject(), this, p -> { project = p; update(); }); diff --git a/ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutPointOfSaleView.java b/ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutPointOfSaleView.java index 2bbddbc0c3..f7c81f4b94 100644 --- a/ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutPointOfSaleView.java +++ b/ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutPointOfSaleView.java @@ -7,8 +7,6 @@ import android.widget.FrameLayout; import android.widget.TextView; -import androidx.lifecycle.Observer; - import java.util.Objects; import io.snabble.sdk.BarcodeFormat; @@ -20,7 +18,7 @@ import io.snabble.sdk.ui.R; import io.snabble.sdk.ui.scanner.BarcodeView; import io.snabble.sdk.ui.utils.OneShotClickListener; -import io.snabble.sdk.ui.utils.ViewExtKt; +import io.snabble.sdk.ui.utils.ViewUtils; import io.snabble.sdk.utils.Dispatch; public class CheckoutPointOfSaleView extends FrameLayout { @@ -47,7 +45,7 @@ private void inflateView() { inflate(getContext(), R.layout.snabble_view_checkout_pos, this); checkout = Objects.requireNonNull(Snabble.getInstance().getCheckedInProject().getValue()).getCheckout(); - ViewExtKt.observeView(Snabble.getInstance().getCheckedInProject(), this, p -> { + ViewUtils.observeView(Snabble.getInstance().getCheckedInProject(), this, p -> { checkout = p.getCheckout(); update(); }); @@ -101,12 +99,7 @@ public void click() { } checkout = Snabble.getInstance().getCheckedInProject().getValue().getCheckout(); - ViewExtKt.observeView(checkout.getState(), this, new Observer() { - @Override - public void onChanged(CheckoutState checkoutState) { - onStateChanged(checkoutState); - } - }); + ViewUtils.observeView(checkout.getState(), this, this::onStateChanged); } public void onStateChanged(CheckoutState state) { diff --git a/ui/src/main/java/io/snabble/sdk/ui/utils/ViewExt.kt b/ui/src/main/java/io/snabble/sdk/ui/utils/ViewExt.kt index 5d0e3624e2..b663e07b67 100644 --- a/ui/src/main/java/io/snabble/sdk/ui/utils/ViewExt.kt +++ b/ui/src/main/java/io/snabble/sdk/ui/utils/ViewExt.kt @@ -1,3 +1,4 @@ +@file:JvmName("ViewUtils") package io.snabble.sdk.ui.utils import android.content.Context From 323f94d287428985fc9a401c57581a61f1e5a18f Mon Sep 17 00:00:00 2001 From: Alexander Junggeburth Date: Mon, 16 May 2022 16:36:28 +0200 Subject: [PATCH 7/9] update docs --- README.md | 56 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 558dfa207b..7d9d4dc2c4 100644 --- a/README.md +++ b/README.md @@ -62,31 +62,59 @@ Then add the library to your dependencies. (Note: The + means it always uses the dependencies { implementation 'io.snabble.sdk:core:+' implementation 'io.snabble.sdk:ui:+' - implementation 'io.snabble.sdk:ui-integration:+' } ``` ## Usage + +You can initialize the SDK by specifying meta-data in the AndroidManifest.xml + +``` + + + +``` + +You also can initialize the SDK programmatically by specifying in the AndroidManifest.xml: + +``` + +``` + +And then initialize the SDK via code. + ```kotlin val config = Config( appId = YOUR_APP_ID, secret = YOUR_SECRET, ) +Snabble.setup(application, config) +``` -// you may enable debug logging -Snabble.setDebugLoggingEnabled(BuildConfig.DEBUG) - -Snabble.setup(application, config, object : Snabble.SetupCompletionListener { - override fun onReady() { - // an application can have multiple projects, for example for - // multiple independent regions / countries - val project = Snabble.projects.first() +To observe the current initialization state of the SDK use: - // check in to the first shop - you can use CheckInManager if you want - // to use geofencing - Snabble.checkedInShop = project.shops.first() +```kotlin +Snabble.initializationState.observe(this) { + when(it) { + InitializationState.INITIALIZING -> {} + InitializationState.INITIALIZED -> { + val projects = Snabble.projects // access to projects + } + InitializationState.ERROR -> { + // error detailing why the initialization failed + val error = Snabble.error + // you can call setup again without arguments to retry the initialization + // e.g. on network failure + Snabble.setup() + } } -}) +} ``` ## Light mode themes @@ -94,7 +122,7 @@ Snabble.setup(application, config, object : Snabble.SetupCompletionListener { If using a theme that is explicitly only light mode (and not a DayNight theme) you need to set ``` - AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); +AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); ``` or else some resources may get grabbed from the "-night" folders when the device is set to night mode From 4b8e96d560696dcb1ca8378a75d11aaa05f08c99 Mon Sep 17 00:00:00 2001 From: Alexander Junggeburth Date: Tue, 17 May 2022 11:07:32 +0200 Subject: [PATCH 8/9] fix tests and cleanup --- README.md | 6 ++-- core/src/main/java/io/snabble/sdk/Config.kt | 8 ++--- core/src/main/java/io/snabble/sdk/Snabble.kt | 11 ++++-- .../java/io/snabble/sdk/SnabbleInitializer.kt | 2 +- docs/docs/sample-apps.md | 35 ++----------------- java-sample/README.md | 22 +----------- kotlin-customization-sample/README.md | 20 ----------- kotlin-sample/README.md | 20 ----------- .../snabble/sdk/sample/LoggingInterceptor.kt | 2 ++ .../sdk/ui/checkout/CheckoutActivity.kt | 5 ++- 10 files changed, 25 insertions(+), 106 deletions(-) diff --git a/README.md b/README.md index 7d9d4dc2c4..710fdcd178 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ dependencies { ## Usage -You can initialize the SDK by specifying meta-data in the AndroidManifest.xml +You can initialize the SDK by specifying metadata in the AndroidManifest.xml ``` { override fun onChanged(t: InitializationState?) { - countDownLatch.countDown() - initializationState.removeObserver(this) + if (t == InitializationState.INITIALIZED || t == InitializationState.ERROR) { + countDownLatch.countDown() + initializationState.removeObserver(this) + } } } initializationState.observeForever(observer) @@ -742,6 +744,9 @@ object Snabble { } } + /** + * Enum describing the error types, which can occur during initialization of the SDK. + */ enum class Error { UNSPECIFIED_ERROR, NO_APPLICATION_SET, diff --git a/core/src/main/java/io/snabble/sdk/SnabbleInitializer.kt b/core/src/main/java/io/snabble/sdk/SnabbleInitializer.kt index 365a3733ac..6dd881e2ef 100644 --- a/core/src/main/java/io/snabble/sdk/SnabbleInitializer.kt +++ b/core/src/main/java/io/snabble/sdk/SnabbleInitializer.kt @@ -38,7 +38,7 @@ class SnabbleInitializer : Initializer { networkInterceptor = try { Class.forName(getString("snabble_network_interceptor", null)).newInstance() as Interceptor? - } catch (e: Exception) { + } catch (e: Throwable) { Logger.d("Could not instantiate network interceptor", e.message) null } diff --git a/docs/docs/sample-apps.md b/docs/docs/sample-apps.md index 2530d17d9b..75978d36bf 100644 --- a/docs/docs/sample-apps.md +++ b/docs/docs/sample-apps.md @@ -1,35 +1,4 @@ # Sample apps -We have two sample apps one legacy app written in Java and a newer written in Kotlin. You can find -both apps in the git repository in the directories [java-sample] and [kotlin-sample]. - -## Requirements - -Before you can start you need an app secrets which you can get via our sales team. You can find the -contact details on our [Homepage](https://snabble.io/en/contact). - -The app secrets can be configures in your `local.properties` file (in the project root directory), -the sample app's `build.gradle` file or with code. - -We recommend using the `local.properties` file. You cannot leak this way your secret accidentally, -since this file is by default in your `.gitignore`. This way you can also configure both sample apps -at once. In that file you need to add these three lines: - -``` -snabble.appId= -snabble.endpoint= -snabble.secret= -``` - -If you want to add it to your `build.gradle` file search for the comment ending with -`Snabble secrets` and put your secrets there. - -The third way is to setup the config via code: - - -[Kotlin](../../kotlin-sample/src/main/java/io/snabble/sdk/sample/LoadingActivity.kt) inside_block:config -[Java](../../java-sample/src/main/java/io/snabble/testapp/App.java) inside_block:config - - - [java-sample]: https://github.com/snabble/Android-SDK/tree/master/java-sample - [kotlin-sample]: https://github.com/snabble/Android-SDK/tree/master/kotlin-sample \ No newline at end of file +We have three sample apps one legacy app written in Java and a two newer written in Kotlin. You can find +both apps in the git repository in the directories [java-sample], [kotlin-sample] and [kotlin-customization-sample]. \ No newline at end of file diff --git a/java-sample/README.md b/java-sample/README.md index c0374413fc..7e5316ff70 100644 --- a/java-sample/README.md +++ b/java-sample/README.md @@ -2,24 +2,4 @@ This is our sample App to show you how to integrate our SDK in a Java app using classic Activity navigation. We have also a newer Kotlin App using modern Navigation components which you can find in -the [kotlin-sample](../kotlin-sample) directory. - -## Requirements - -Before you can start you need an app secrets which you can get via our sales team. You can find the -contact details on our [Homepage](https://snabble.io/en/contact). - -The app secrets can be configures in your `local.properties` file (in the project root directory), -the sample app's `build.gradle` file or directly in the `Config` object in `init` function of the -[`App`](src/main/java/io/snabble/testapp/App.java). - -In the `local.properties` file you need to add these three lines: - -``` -snabble.appId= -snabble.endpoint= -snabble.secret= -``` - -If you want to add it to your `build.gradle` file search for the comment ending with -`Snabble secrets` and put your secrets there. \ No newline at end of file +the [kotlin-sample](../kotlin-sample) directory. \ No newline at end of file diff --git a/kotlin-customization-sample/README.md b/kotlin-customization-sample/README.md index c3815473da..55e327693f 100644 --- a/kotlin-customization-sample/README.md +++ b/kotlin-customization-sample/README.md @@ -10,23 +10,3 @@ enabled. Please also check the other two sample apps in [kotlin-sample](../kotli This is a tech demo what you can currently do with the Android SDK. That does not mean that the same features are currently implemented in the iOS SDK too. The custom dialog confirmation dialog is a technical preview and not yet supported on iOS. - -## Requirements - -Before you can start you need an app secrets which you can get via our sales team. You can find the -contact details on our [Homepage](https://snabble.io/en/contact). - -The app secrets can be configures in your `local.properties` file (in the project root directory), -the sample app's `build.gradle` file or directly in the `Config` object in `initSdk` function of -[`LoadingActivity`](src/main/java/io/snabble/sdk/customization/LoadingActivity.kt). - -In the `local.properties` file you need to add these three lines: - -``` -snabble.appId= -snabble.endpoint= -snabble.secret= -``` - -If you want to add it to your `build.gradle` file search for the comment ending with -`Snabble secrets` and put your secrets there. \ No newline at end of file diff --git a/kotlin-sample/README.md b/kotlin-sample/README.md index d8d22fdae7..af4cb273d9 100644 --- a/kotlin-sample/README.md +++ b/kotlin-sample/README.md @@ -3,23 +3,3 @@ This is our sample App to show you how to integrate our SDK in a Kotlin app using modern Navigation components. If you use classic Activity navigation, you can check out our Java Sample App in the [java-sample](../java-sample) directory. - -## Requirements - -Before you can start you need an app secrets which you can get via our sales team. You can find the -contact details on our [Homepage](https://snabble.io/en/contact). - -The app secrets can be configures in your `local.properties` file (in the project root directory), -the sample app's `build.gradle` file or directly in the `Config` object in `initSdk` function of -[`LoadingActivity`](src/main/java/io/snabble/sdk/sample/LoadingActivity.kt). - -In the `local.properties` file you need to add these three lines: - -``` -snabble.appId= -snabble.endpoint= -snabble.secret= -``` - -If you want to add it to your `build.gradle` file search for the comment ending with -`Snabble secrets` and put your secrets there. \ No newline at end of file diff --git a/kotlin-sample/src/main/java/io/snabble/sdk/sample/LoggingInterceptor.kt b/kotlin-sample/src/main/java/io/snabble/sdk/sample/LoggingInterceptor.kt index 4be4992e46..b21ebb8417 100644 --- a/kotlin-sample/src/main/java/io/snabble/sdk/sample/LoggingInterceptor.kt +++ b/kotlin-sample/src/main/java/io/snabble/sdk/sample/LoggingInterceptor.kt @@ -1,9 +1,11 @@ package io.snabble.sdk.sample import android.util.Log +import androidx.annotation.Keep import okhttp3.Interceptor import okhttp3.Response +@Keep class LoggingInterceptor : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val url = chain.call().request().url diff --git a/ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutActivity.kt b/ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutActivity.kt index cebe8660ea..1b4bd2c9a8 100644 --- a/ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutActivity.kt +++ b/ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutActivity.kt @@ -54,7 +54,10 @@ class CheckoutActivity : FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - Snabble.setup(application) + if (Snabble.initializationState.value != InitializationState.INITIALIZED) { + Snabble.setup(application) + } + Snabble.initializationState.observe(this) { when (it) { InitializationState.INITIALIZED -> { From 42424f2c88d925fefc29851d7dba00a8e13ae9f3 Mon Sep 17 00:00:00 2001 From: Alexander Junggeburth Date: Tue, 17 May 2022 11:35:17 +0200 Subject: [PATCH 9/9] migrate to MutableAccessibleLiveData and add UNINITIALIZED state --- .../main/java/io/snabble/sdk/InitializationState.kt | 1 + core/src/main/java/io/snabble/sdk/Snabble.kt | 10 +++++----- .../io/snabble/sdk/customization/LoadingActivity.kt | 1 + .../main/java/io/snabble/sdk/sample/LoadingActivity.kt | 1 + ui/src/main/java/io/snabble/sdk/ui/BaseFragment.kt | 1 + .../io/snabble/sdk/ui/checkout/CheckoutActivity.kt | 5 ++--- 6 files changed, 11 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/io/snabble/sdk/InitializationState.kt b/core/src/main/java/io/snabble/sdk/InitializationState.kt index e2d4df267b..a2c137b832 100644 --- a/core/src/main/java/io/snabble/sdk/InitializationState.kt +++ b/core/src/main/java/io/snabble/sdk/InitializationState.kt @@ -1,6 +1,7 @@ package io.snabble.sdk enum class InitializationState { + UNINITIALIZED, INITIALIZING, INITIALIZED, ERROR, diff --git a/core/src/main/java/io/snabble/sdk/Snabble.kt b/core/src/main/java/io/snabble/sdk/Snabble.kt index dddddab170..94176c6528 100644 --- a/core/src/main/java/io/snabble/sdk/Snabble.kt +++ b/core/src/main/java/io/snabble/sdk/Snabble.kt @@ -226,7 +226,7 @@ object Snabble { var createAppUserUrl: String? = null private set - private val mutableInitializationState = MutableLiveData() + private val mutableInitializationState = MutableAccessibleLiveData(InitializationState.UNINITIALIZED) /** * The current initialization state of the SDK. Fragments observe this state and only @@ -313,7 +313,7 @@ object Snabble { } isInitializing.set(true) - mutableInitializationState.postValue(InitializationState.INITIALIZING) + mutableInitializationState.value = InitializationState.INITIALIZING if (app != null) { application = app @@ -420,13 +420,13 @@ object Snabble { val token = tokenRegistry.getToken(projects[0]) if (token == null) { isInitializing.set(false) - mutableInitializationState.postValue(InitializationState.ERROR) + mutableInitializationState.value = InitializationState.ERROR return@background } } isInitializing.set(false) - mutableInitializationState.postValue(InitializationState.INITIALIZED) + mutableInitializationState.value = InitializationState.INITIALIZED if (config.loadActiveShops) { loadActiveShops() @@ -436,7 +436,7 @@ object Snabble { private fun dispatchError(error: Error) { isInitializing.set(false) - mutableInitializationState.postValue(InitializationState.ERROR) + mutableInitializationState.value = InitializationState.ERROR this.error = error } diff --git a/kotlin-customization-sample/src/main/java/io/snabble/sdk/customization/LoadingActivity.kt b/kotlin-customization-sample/src/main/java/io/snabble/sdk/customization/LoadingActivity.kt index 2007d3bf93..e49dd1785f 100644 --- a/kotlin-customization-sample/src/main/java/io/snabble/sdk/customization/LoadingActivity.kt +++ b/kotlin-customization-sample/src/main/java/io/snabble/sdk/customization/LoadingActivity.kt @@ -18,6 +18,7 @@ class LoadingActivity : AppCompatActivity() { Snabble.setDebugLoggingEnabled(BuildConfig.DEBUG) Snabble.initializationState.observe(this) { when (it) { + InitializationState.UNINITIALIZED, InitializationState.INITIALIZING -> {} InitializationState.INITIALIZED -> { // an application can have multiple projects, for example for diff --git a/kotlin-sample/src/main/java/io/snabble/sdk/sample/LoadingActivity.kt b/kotlin-sample/src/main/java/io/snabble/sdk/sample/LoadingActivity.kt index ff22186072..9fc4c7a65e 100644 --- a/kotlin-sample/src/main/java/io/snabble/sdk/sample/LoadingActivity.kt +++ b/kotlin-sample/src/main/java/io/snabble/sdk/sample/LoadingActivity.kt @@ -19,6 +19,7 @@ class LoadingActivity : AppCompatActivity() { Snabble.setDebugLoggingEnabled(BuildConfig.DEBUG) Snabble.initializationState.observe(this) { when (it) { + InitializationState.UNINITIALIZED, InitializationState.INITIALIZING -> {} InitializationState.INITIALIZED -> { // an application can have multiple projects, for example for diff --git a/ui/src/main/java/io/snabble/sdk/ui/BaseFragment.kt b/ui/src/main/java/io/snabble/sdk/ui/BaseFragment.kt index cee1c00107..3f2a5aca9d 100644 --- a/ui/src/main/java/io/snabble/sdk/ui/BaseFragment.kt +++ b/ui/src/main/java/io/snabble/sdk/ui/BaseFragment.kt @@ -37,6 +37,7 @@ abstract class BaseFragment(@LayoutRes val layoutResId: Int = 0, val waitForProj InitializationState.ERROR -> { sdkNotInitialized.isVisible = true } + InitializationState.UNINITIALIZED, InitializationState.INITIALIZING -> {} // ignore } } diff --git a/ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutActivity.kt b/ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutActivity.kt index 1b4bd2c9a8..afe9dd09b8 100644 --- a/ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutActivity.kt +++ b/ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutActivity.kt @@ -99,9 +99,8 @@ class CheckoutActivity : FragmentActivity() { navController.graph = navGraph } } - InitializationState.INITIALIZING -> { - // ignore - } + InitializationState.UNINITIALIZED, + InitializationState.INITIALIZING -> {} // ignore InitializationState.ERROR -> { finishWithError("The snabble SDK is not initialized") }