diff --git a/README.md b/README.md
index 558dfa207b..710fdcd178 100644
--- a/README.md
+++ b/README.md
@@ -62,39 +62,67 @@ 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 metadata 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
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);
+```kotlin
+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
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..c610edc5e8 100644
--- a/core/src/main/java/io/snabble/sdk/Config.kt
+++ b/core/src/main/java/io/snabble/sdk/Config.kt
@@ -1,6 +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 (
@@ -135,4 +143,64 @@ 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 = gson.toJson(this)
+ Dispatch.io {
+ try {
+ file.writeText(json)
+ } catch (e: Throwable) {
+ Logger.e("Failed to save config to ${file.path}: ${e.message}")
+ }
+ }
+ }
+
+ companion object {
+ 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 = gson.fromJson(text, Config::class.java)
+ config
+ } catch (e: Throwable) {
+ Logger.e("Failed to load config to ${file.path}: ${e.message}")
+ null
+ }
+ }
+ }
+}
+
+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/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/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 ea9060991d..94176c6528 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
@@ -225,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
@@ -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.
*
@@ -293,34 +300,56 @@ 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 setupCompletionListener Completion listener that gets called when the SDK is ready.
+ * @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, setupCompletionListener: SetupCompletionListener? = null) {
+ fun setup(app: Application? = null, config: Config? = null) {
if (isInitializing.get()) {
return
}
isInitializing.set(true)
- mutableInitializationState.postValue(InitializationState.INITIALIZING)
+ mutableInitializationState.value = InitializationState.INITIALIZING
+
+ if (app != null) {
+ application = app
+ }
+
+ if (!this::application.isInitialized) {
+ dispatchError(Error.NO_APPLICATION_SET)
+ return
+ }
- application = app
- this.config = config
+ if (config == null) {
+ val restoredConfig = Config.restore(application)
+ if (restoredConfig == null) {
+ dispatchError(Error.CONFIG_ERROR)
+ return
+ } else {
+ this.config = restoredConfig
+ }
+ } else {
+ this.config = config
+ config.save(application)
+ }
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://")) {
- setupCompletionListener?.onError(Error.CONFIG_ERROR)
+ if (!this.config.endpointBaseUrl.startsWith("http://")
+ && !this.config.endpointBaseUrl.startsWith("https://")) {
+ dispatchError(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)
+ val pInfo = application.packageManager.getPackageInfo(application.packageName, 0)
pInfo?.versionName?.lowercase(Locale.ROOT)?.replace(" ", "") ?: "1.0"
} catch (e: PackageManager.NameNotFoundException) {
"1.0"
@@ -328,49 +357,49 @@ 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)
+ okHttpClient = OkHttpClientFactory.createOkHttpClient(application)
+ userPreferences = UserPreferences(application)
+ 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) {
- dispatchOnReady(setupCompletionListener)
+ if (this.config.bundledMetadataAssetPath != null) {
+ 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)
}
}
})
}
- app.registerActivityLifecycleCallbacks(activityLifecycleCallbacks)
+ application.registerActivityLifecycleCallbacks(activityLifecycleCallbacks)
ProcessLifecycleOwner.get().lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) {
@@ -380,10 +409,10 @@ object Snabble {
}
})
- registerNetworkCallback(app)
+ registerNetworkCallback(application)
}
- private fun dispatchOnReady(setupCompletionListener: SetupCompletionListener?) {
+ private fun dispatchOnReady() {
Dispatch.background {
readMetadata()
val appUser = userPreferences.appUser
@@ -391,33 +420,24 @@ object Snabble {
val token = tokenRegistry.getToken(projects[0])
if (token == null) {
isInitializing.set(false)
- mutableInitializationState.postValue(InitializationState.ERROR)
-
- Dispatch.mainThread {
- setupCompletionListener?.onError(Error.CONNECTION_TIMEOUT)
- }
+ mutableInitializationState.value = InitializationState.ERROR
return@background
}
}
isInitializing.set(false)
- mutableInitializationState.postValue(InitializationState.INITIALIZED)
- Dispatch.mainThread {
- setupCompletionListener?.onReady()
- }
+ mutableInitializationState.value = InitializationState.INITIALIZED
+
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)
- }
+ mutableInitializationState.value = InitializationState.ERROR
+ this.error = error
}
/**
@@ -432,19 +452,20 @@ 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
- countDownLatch.countDown()
+ setup(app, config)
+ val observer = object : Observer {
+ override fun onChanged(t: InitializationState?) {
+ if (t == InitializationState.INITIALIZED || t == InitializationState.ERROR) {
+ countDownLatch.countDown()
+ initializationState.removeObserver(this)
+ }
}
- })
+ }
+ initializationState.observeForever(observer)
try {
countDownLatch.await()
} catch (e: InterruptedException) {
@@ -715,11 +736,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{" +
@@ -728,8 +744,12 @@ object Snabble {
}
}
+ /**
+ * Enum describing the error types, which can occur during initialization of the SDK.
+ */
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
new file mode 100644
index 0000000000..6dd881e2ef
--- /dev/null
+++ b/core/src/main/java/io/snabble/sdk/SnabbleInitializer.kt
@@ -0,0 +1,60 @@
+package io.snabble.sdk
+
+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.
+ */
+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)
+ networkInterceptor =
+ try {
+ Class.forName(getString("snabble_network_interceptor", null)).newInstance() as Interceptor?
+ } catch (e: Throwable) {
+ 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
+ }
+ }
+
+ override fun dependencies(): List>> {
+ return emptyList()
+ }
+}
\ No newline at end of file
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/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/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/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-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..f059534e76 100644
--- a/kotlin-customization-sample/src/main/AndroidManifest.xml
+++ b/kotlin-customization-sample/src/main/AndroidManifest.xml
@@ -28,11 +28,18 @@
+
+
+
+
\ 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..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
@@ -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,46 @@ 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.UNINITIALIZED,
+ 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(Snabble.error?.name)
+ .setPositiveButton("Retry") { _, _ ->
+ Snabble.setup()
+ }
+ .setNegativeButton("Exit") { _, _ ->
+ finish()
+ }
+ .create()
+ .show()
+ }
}
}
- })
+ }
}
}
\ 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/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..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,18 @@
+
+
+
+
+
+
\ 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..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
@@ -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,46 @@ 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.UNINITIALIZED,
+ 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(Snabble.error?.name)
+ .setPositiveButton("Retry") { _, _ ->
+ Snabble.setup()
+ }
+ .setNegativeButton("Exit") { _, _ ->
+ finish()
+ }
+ .create()
+ .show()
+ }
}
}
- })
+ }
}
}
\ 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
new file mode 100644
index 0000000000..b21ebb8417
--- /dev/null
+++ b/kotlin-sample/src/main/java/io/snabble/sdk/sample/LoggingInterceptor.kt
@@ -0,0 +1,15 @@
+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
+ 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/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/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..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
@@ -54,6 +54,10 @@ class CheckoutActivity : FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ if (Snabble.initializationState.value != InitializationState.INITIALIZED) {
+ Snabble.setup(application)
+ }
+
Snabble.initializationState.observe(this) {
when (it) {
InitializationState.INITIALIZED -> {
@@ -95,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")
}
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