diff --git a/android/src/main/java/com/amplitude/android/utilities/AndroidStorage.kt b/android/src/main/java/com/amplitude/android/utilities/AndroidStorage.kt index 66df52bc..437e2201 100644 --- a/android/src/main/java/com/amplitude/android/utilities/AndroidStorage.kt +++ b/android/src/main/java/com/amplitude/android/utilities/AndroidStorage.kt @@ -92,7 +92,10 @@ class AndroidStorage( } override fun getEventCallback(insertId: String): EventCallBack? { - return eventCallbacksMap.getOrDefault(insertId, null) + if (eventCallbacksMap.contains(insertId)) { + return eventCallbacksMap.get(insertId) + } + return null } override fun removeEventCallback(insertId: String) { diff --git a/core/src/main/java/com/amplitude/core/Amplitude.kt b/core/src/main/java/com/amplitude/core/Amplitude.kt index 3db6eaa3..9f9e30b5 100644 --- a/core/src/main/java/com/amplitude/core/Amplitude.kt +++ b/core/src/main/java/com/amplitude/core/Amplitude.kt @@ -29,6 +29,11 @@ import kotlinx.coroutines.launch import org.json.JSONObject import java.util.concurrent.Executors +/** + *

Amplitude

+ * This is the SDK instance class that contains all of the SDK functionality.

+ * Many of the SDK functions return the SDK instance back, allowing you to chain multiple methods calls together. + */ open class Amplitude internal constructor( val configuration: Configuration, val store: State, @@ -52,7 +57,7 @@ open class Amplitude internal constructor( } /** - * Public Constructor + * Public Constructor. */ constructor(configuration: Configuration) : this(configuration, State()) @@ -68,18 +73,34 @@ open class Amplitude internal constructor( } @Deprecated("Please use 'track' instead.", ReplaceWith("track")) - fun logEvent(event: BaseEvent) { - track(event) + fun logEvent(event: BaseEvent): Amplitude { + return track(event) } - fun track(event: BaseEvent, callback: EventCallBack? = null) { + /** + * Track an event. + * + * @param event the event + * @param callback the optional event callback + * @return the Amplitude instance + */ + fun track(event: BaseEvent, callback: EventCallBack? = null): Amplitude { callback ?. let { event.callback = it } process(event) + return this } - fun track(eventType: String, eventProperties: JSONObject? = null, options: EventOptions? = null) { + /** + * Log event with the specified event type, event properties, and optional event options. + * + * @param eventType the event type + * @param eventProperties the event properties + * @param options optional event options + * @return the Amplitude instance + */ + fun track(eventType: String, eventProperties: JSONObject? = null, options: EventOptions? = null): Amplitude { val event = BaseEvent() event.eventType = eventType event.eventProperties = eventProperties @@ -87,35 +108,65 @@ open class Amplitude internal constructor( event.mergeEventOptions(it) } process(event) + return this } /** - * Identify. Use this to send an Identify object containing user property operations to Amplitude server. + * Identify lets you to send an Identify object containing user property operations to Amplitude server. + * You can modify user properties by calling this api. + * + * @param identify identify object + * @param options optional event options + * @return the Amplitude instance */ - fun identify(identify: Identify, options: EventOptions? = null) { + fun identify(identify: Identify, options: EventOptions? = null): Amplitude { val event = IdentifyEvent() event.userProperties = identify.properties options ?. let { event.mergeEventOptions(it) } process(event) + return this } - fun identify(userId: String) { + /** + * Set the user id (can be null). + * + * @param userId custom user id + * @return the Amplitude instance + */ + fun setUserId(userId: String?): Amplitude { this.idContainer.identityManager.editIdentity().setUserId(userId).commit() + return this } - fun setDeviceId(deviceId: String) { + /** + * Sets a custom device id. Note: only do this if you know what you are doing! + * + * @param deviceId custom device id + * @return the Amplitude instance + */ + fun setDeviceId(deviceId: String): Amplitude { this.idContainer.identityManager.editIdentity().setDeviceId(deviceId).commit() + return this } - fun groupIdentify(groupType: String, groupName: String, identify: Identify, options: EventOptions? = null) { + /** + * Identify a group. You can modify group properties by calling this api. + * + * @param groupType the group type + * @param groupName the group name + * @param identify identify object + * @param options optional event options + * @return the Amplitude instance + */ + fun groupIdentify(groupType: String, groupName: String, identify: Identify, options: EventOptions? = null): Amplitude { val event = GroupIdentifyEvent() var group: JSONObject? = null try { group = JSONObject().put(groupType, groupName) } catch (e: Exception) { - logger.error(e.toString()) + logger.error("Error in groupIdentify: $e") } event.groups = group event.groupProperties = identify.properties @@ -123,49 +174,73 @@ open class Amplitude internal constructor( event.mergeEventOptions(it) } process(event) + return this } /** - * Sets the user's group. + * Set the user's group. + * + * @param groupType the group type + * @param groupName the group name + * @param options optional event options + * @return the Amplitude instance */ - fun setGroup(groupType: String, groupName: String, options: EventOptions? = null) { + fun setGroup(groupType: String, groupName: String, options: EventOptions? = null): Amplitude { val identify = Identify().set(groupType, groupName) identify(identify, options) + return this } /** - * ets the user's groups. + * Sets the user's groups. + * + * @param groupType the group type + * @param groupName the group name + * @param options optional event options + * @return the Amplitude instance */ - fun setGroup(groupType: String, groupName: Array, options: EventOptions? = null) { + fun setGroup(groupType: String, groupName: Array, options: EventOptions? = null): Amplitude { val identify = Identify().set(groupType, groupName) identify(identify, options) + return this } @Deprecated("Please use 'revenue' instead.", ReplaceWith("revenue")) - fun logRevenue(revenue: Revenue) { + fun logRevenue(revenue: Revenue): Amplitude { revenue(revenue) + return this } /** * Create a Revenue object to hold your revenue data and properties, * and log it as a revenue event using this method. + * + * @param revenue revenue object + * @param options optional event options + * @return the Amplitude instance */ - fun revenue(revenue: Revenue, options: EventOptions? = null) { + fun revenue(revenue: Revenue, options: EventOptions? = null): Amplitude { if (!revenue.isValid()) { - return + logger.warn("Invalid revenue object, missing required fields") + return this } val event = revenue.toRevenueEvent() options ?. let { event.mergeEventOptions(it) } revenue(event) + return this } /** - * Log a Revenue Event + * Log a Revenue Event. + * + * @param event the revenue event + * @return the Amplitude instance */ - fun revenue(event: RevenueEvent) { + fun revenue(event: RevenueEvent): Amplitude { process(event) + return this } private fun process(event: BaseEvent) { @@ -177,6 +252,12 @@ open class Amplitude internal constructor( } } + /** + * Add a plugin. + * + * @param plugin the plugin + * @return the Amplitude instance + */ fun add(plugin: Plugin): Amplitude { when (plugin) { is ObservePlugin -> { diff --git a/core/src/main/java/com/amplitude/core/events/Identify.kt b/core/src/main/java/com/amplitude/core/events/Identify.kt index e92d9341..68e2295a 100644 --- a/core/src/main/java/com/amplitude/core/events/Identify.kt +++ b/core/src/main/java/com/amplitude/core/events/Identify.kt @@ -569,7 +569,7 @@ class Identify() { properties.getJSONObject(operation.operationType).put(property, value) propertySet.add(property) } catch (e: Exception) { - ConsoleLogger.logger.error(e.toString()) + ConsoleLogger.logger.error("Error in set user property: $e") } } diff --git a/core/src/main/java/com/amplitude/core/platform/EventPipeline.kt b/core/src/main/java/com/amplitude/core/platform/EventPipeline.kt index 305250b2..092ef137 100644 --- a/core/src/main/java/com/amplitude/core/platform/EventPipeline.kt +++ b/core/src/main/java/com/amplitude/core/platform/EventPipeline.kt @@ -80,7 +80,7 @@ class EventPipeline( storage.writeEvent(message.event) } catch (e: Exception) { e.message?.let { - amplitude.logger.error(it) + amplitude.logger.error("Error when write event: $it") } } @@ -117,7 +117,7 @@ class EventPipeline( responseHandler?.handle(connection.response) } catch (e: Exception) { e.message?.let { - amplitude.logger.error(it) + amplitude.logger.error("Error when upload event: $it") } } } diff --git a/core/src/main/java/com/amplitude/core/utilities/JSONUtil.kt b/core/src/main/java/com/amplitude/core/utilities/JSONUtil.kt index 7029ceb5..e65cd2e7 100644 --- a/core/src/main/java/com/amplitude/core/utilities/JSONUtil.kt +++ b/core/src/main/java/com/amplitude/core/utilities/JSONUtil.kt @@ -48,6 +48,7 @@ object JSONUtil { eventJSON.addValue("insert_id", event.insertId) eventJSON.addValue("library", event.library) eventJSON.addValue("partner_id", event.partnerId) + eventJSON.addValue("android_app_set_id", event.appSetId) event.plan?. let { eventJSON.put("plan", it.toJSONObject()) } @@ -157,50 +158,51 @@ internal fun JSONArray.toIntArray(): IntArray { internal fun JSONObject.toBaseEvent(): BaseEvent { val event = BaseEvent() event.eventType = this.getString("event_type") - event.userId = this.optString("user_id", null) - event.deviceId = this.optString("device_id", null) + event.userId = this.optionalString("user_id", null) + event.deviceId = this.optionalString("device_id", null) event.timestamp = if (this.has("time")) this.getLong("time") else null - event.eventProperties = this.optJSONObject("event_properties", null) - event.userProperties = this.optJSONObject("user_properties", null) - event.groups = this.optJSONObject("groups", null) - event.appVersion = this.optString("app_version", null) - event.platform = this.optString("platform", null) - event.osName = this.optString("os_name", null) - event.osVersion = this.optString("os_version", null) - event.deviceBrand = this.optString("device_brand", null) - event.deviceManufacturer = this.optString("device_manufacturer", null) - event.deviceModel = this.optString("device_model", null) - event.carrier = this.optString("carrier", null) - event.country = this.optString("country", null) - event.region = this.optString("region", null) - event.city = this.optString("city", null) - event.dma = this.optString("dma", null) - event.language = this.optString("language", null) + event.eventProperties = this.optionalJSONObject("event_properties", null) + event.userProperties = this.optionalJSONObject("user_properties", null) + event.groups = this.optionalJSONObject("groups", null) + event.appVersion = this.optionalString("app_version", null) + event.platform = this.optionalString("platform", null) + event.osName = this.optionalString("os_name", null) + event.osVersion = this.optionalString("os_version", null) + event.deviceBrand = this.optionalString("device_brand", null) + event.deviceManufacturer = this.optionalString("device_manufacturer", null) + event.deviceModel = this.optionalString("device_model", null) + event.carrier = this.optionalString("carrier", null) + event.country = this.optionalString("country", null) + event.region = this.optionalString("region", null) + event.city = this.optionalString("city", null) + event.dma = this.optionalString("dma", null) + event.language = this.optionalString("language", null) event.price = if (this.has("price")) this.getDouble("price") else null event.quantity = if (this.has("quantity")) this.getInt("quantity") else null event.revenue = if (this.has("revenue")) this.getDouble("revenue") else null - event.productId = this.optString("productId", null) - event.revenueType = this.optString("revenueType", null) + event.productId = this.optionalString("productId", null) + event.revenueType = this.optionalString("revenueType", null) event.locationLat = if (this.has("location_lat")) this.getDouble("location_lat") else null event.locationLng = if (this.has("location_lng")) this.getDouble("location_lng") else null - event.ip = this.optString("ip", null) - event.idfa = this.optString("idfa", null) - event.idfv = this.optString("idfv", null) - event.adid = this.optString("adid", null) - event.androidId = this.optString("android_id", null) + event.ip = this.optionalString("ip", null) + event.idfa = this.optionalString("idfa", null) + event.idfv = this.optionalString("idfv", null) + event.adid = this.optionalString("adid", null) + event.androidId = this.optionalString("android_id", null) + event.appSetId = this.optString("android_app_set_id", null) event.eventId = if (this.has("event_id")) this.getInt("event_id") else null event.sessionId = this.getLong("session_id") - event.insertId = this.optString("insert_id", null) + event.insertId = this.optionalString("insert_id", null) event.library = if (this.has("library")) this.getString("library") else null - event.partnerId = this.optString("partner_id", null) + event.partnerId = this.optionalString("partner_id", null) event.plan = if (this.has("plan")) Plan.fromJSONObject(this.getJSONObject("plan")) else null return event } internal fun JSONArray.toEvents(): List { val events = mutableListOf() - this.forEach { - events.add((it as JSONObject).toBaseEvent()) + (0 until this.length()).forEach { + events.add((this.getJSONObject(it)).toBaseEvent()) } return events } @@ -209,11 +211,11 @@ internal fun JSONArray.split(): Pair { val mid = this.length() / 2 val firstHalf = JSONArray() val secondHalf = JSONArray() - this.forEachIndexed { index, obj -> + (0 until this.length()).forEach { index, -> if (index < mid) { - firstHalf.put(obj) + firstHalf.put(this.getJSONObject(index)) } else { - secondHalf.put(obj) + secondHalf.put(this.getJSONObject(index)) } } return Pair(firstHalf.toString(), secondHalf.toString()) @@ -224,3 +226,17 @@ internal fun JSONObject.addValue(key: String, value: Any?) { this.put(key, value) } } + +inline fun JSONObject.optionalJSONObject(key: String, defaultValue: JSONObject?): JSONObject? { + if (this.has(key)) { + return this.getJSONObject(key) + } + return defaultValue +} + +inline fun JSONObject.optionalString(key: String, defaultValue: String?): String? { + if (this.has(key)) { + return this.getString(key) + } + return defaultValue +} diff --git a/samples/kotlin-android-app/build.gradle b/samples/kotlin-android-app/build.gradle new file mode 100644 index 00000000..6f46a9e2 --- /dev/null +++ b/samples/kotlin-android-app/build.gradle @@ -0,0 +1,49 @@ +plugins { + id 'com.android.application' + id 'kotlin-android' +} +ext { + AMPLITUDE_API_KEY = "" +} + +android { + compileSdk 31 + + defaultConfig { + applicationId "com.amplitude.android.sample" + minSdk 16 + targetSdk 31 + versionCode 1 + versionName "1.0" + multiDexEnabled true + buildConfigField "String", "AMPLITUDE_API_KEY", "\"${AMPLITUDE_API_KEY}\"" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + lintOptions { + abortOnError false + } +} + +dependencies { + implementation project(':android') + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.1' + implementation 'com.google.android.material:material:1.5.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + implementation 'com.google.android.gms:play-services-ads:20.6.0' + implementation 'com.google.android.gms:play-services-appset:16.0.2' +} diff --git a/samples/kotlin-android-app/proguard-rules.pro b/samples/kotlin-android-app/proguard-rules.pro new file mode 100644 index 00000000..a226f984 --- /dev/null +++ b/samples/kotlin-android-app/proguard-rules.pro @@ -0,0 +1,23 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile +play-services-ads.-keep class com.google.android.gms.ads.** { *; } + diff --git a/samples/kotlin-android-app/src/main/AndroidManifest.xml b/samples/kotlin-android-app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..f37c3b3e --- /dev/null +++ b/samples/kotlin-android-app/src/main/AndroidManifest.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/kotlin-android-app/src/main/java/com/amplitude/android/sample/MainActivity.kt b/samples/kotlin-android-app/src/main/java/com/amplitude/android/sample/MainActivity.kt new file mode 100644 index 00000000..5235b1fc --- /dev/null +++ b/samples/kotlin-android-app/src/main/java/com/amplitude/android/sample/MainActivity.kt @@ -0,0 +1,47 @@ +package com.amplitude.android.sample + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import com.amplitude.core.events.EventOptions +import com.amplitude.core.events.Identify +import com.amplitude.core.events.Plan +import com.amplitude.core.events.Revenue +import org.json.JSONObject + +class MainActivity : AppCompatActivity() { + private val amplitude = MainApplication.amplitude + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + // set user properties + val identify = Identify() + identify.set("user-platform", "android") + .set("custom-properties", "sample") + val options = EventOptions() + options.plan = Plan(branch = "test") + amplitude.identify(identify, options) + + // set groups fro this user + val groupType = "test-group-type" + val groupName = "android-kotlin-sample" + amplitude.setGroup(groupType, groupName) + amplitude.setGroup("orgId", "15") + amplitude.setGroup("sport", arrayOf("tennis", "soccer")) // list values + + // group identify to set group properties + val groupIdentifyObj = Identify().set("key", "value") + amplitude.groupIdentify(groupType, groupName, groupIdentifyObj) + + // log revenue call + val revenue = Revenue() + revenue.productId = "com.company.productId" + revenue.price = 3.99 + revenue.quantity = 3 + amplitude.revenue(revenue) + + // track event with event properties + amplitude.track("test event properties", JSONObject().put("test", "test event property value")) + } +} diff --git a/samples/kotlin-android-app/src/main/java/com/amplitude/android/sample/MainApplication.kt b/samples/kotlin-android-app/src/main/java/com/amplitude/android/sample/MainApplication.kt new file mode 100644 index 00000000..a2d23b50 --- /dev/null +++ b/samples/kotlin-android-app/src/main/java/com/amplitude/android/sample/MainApplication.kt @@ -0,0 +1,44 @@ +package com.amplitude.android.sample + +import android.app.Application +import com.amplitude.android.Amplitude +import com.amplitude.android.Configuration +import com.amplitude.core.events.BaseEvent +import com.amplitude.core.platform.Plugin +import org.json.JSONObject + +class MainApplication : Application() { + companion object { + lateinit var amplitude: Amplitude + const val AMPLITUDE_API_KEY = BuildConfig.AMPLITUDE_API_KEY + } + + override fun onCreate() { + super.onCreate() + + // init instance + amplitude = Amplitude( + Configuration( + apiKey = AMPLITUDE_API_KEY, + context = applicationContext + ) + ) + + // add sample plugin + amplitude.add(object : Plugin { + override val type: Plugin.Type = Plugin.Type.Enrichment + override lateinit var amplitude: com.amplitude.core.Amplitude + + override fun execute(event: BaseEvent): BaseEvent? { + event.eventProperties = event.eventProperties ?: JSONObject() + event.eventProperties?.let { + it.put("custom android event property", "test") + } + return event + } + }) + + // identify a sample user + amplitude.setUserId("android-kotlin-sample-user") + } +} diff --git a/samples/kotlin-android-app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/samples/kotlin-android-app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 00000000..2b068d11 --- /dev/null +++ b/samples/kotlin-android-app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/samples/kotlin-android-app/src/main/res/drawable/ic_launcher_background.xml b/samples/kotlin-android-app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..07d5da9c --- /dev/null +++ b/samples/kotlin-android-app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/kotlin-android-app/src/main/res/layout/activity_main.xml b/samples/kotlin-android-app/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..109b1ab9 --- /dev/null +++ b/samples/kotlin-android-app/src/main/res/layout/activity_main.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/samples/kotlin-android-app/src/main/res/mipmap-hdpi/ic_launcher.webp b/samples/kotlin-android-app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 00000000..c209e78e Binary files /dev/null and b/samples/kotlin-android-app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/samples/kotlin-android-app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/samples/kotlin-android-app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 00000000..b2dfe3d1 Binary files /dev/null and b/samples/kotlin-android-app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/samples/kotlin-android-app/src/main/res/mipmap-mdpi/ic_launcher.webp b/samples/kotlin-android-app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 00000000..4f0f1d64 Binary files /dev/null and b/samples/kotlin-android-app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/samples/kotlin-android-app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/samples/kotlin-android-app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 00000000..62b611da Binary files /dev/null and b/samples/kotlin-android-app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/samples/kotlin-android-app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/samples/kotlin-android-app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 00000000..948a3070 Binary files /dev/null and b/samples/kotlin-android-app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/samples/kotlin-android-app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/samples/kotlin-android-app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 00000000..1b9a6956 Binary files /dev/null and b/samples/kotlin-android-app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/samples/kotlin-android-app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/samples/kotlin-android-app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 00000000..28d4b77f Binary files /dev/null and b/samples/kotlin-android-app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/samples/kotlin-android-app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/samples/kotlin-android-app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 00000000..9287f508 Binary files /dev/null and b/samples/kotlin-android-app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/samples/kotlin-android-app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/samples/kotlin-android-app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 00000000..aa7d6427 Binary files /dev/null and b/samples/kotlin-android-app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/samples/kotlin-android-app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/samples/kotlin-android-app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 00000000..9126ae37 Binary files /dev/null and b/samples/kotlin-android-app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/samples/kotlin-android-app/src/main/res/values-night/themes.xml b/samples/kotlin-android-app/src/main/res/values-night/themes.xml new file mode 100644 index 00000000..5ad6e039 --- /dev/null +++ b/samples/kotlin-android-app/src/main/res/values-night/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/samples/kotlin-android-app/src/main/res/values/colors.xml b/samples/kotlin-android-app/src/main/res/values/colors.xml new file mode 100644 index 00000000..f8c6127d --- /dev/null +++ b/samples/kotlin-android-app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/samples/kotlin-android-app/src/main/res/values/strings.xml b/samples/kotlin-android-app/src/main/res/values/strings.xml new file mode 100644 index 00000000..2f0d4c89 --- /dev/null +++ b/samples/kotlin-android-app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + kotlin-android-app + \ No newline at end of file diff --git a/samples/kotlin-android-app/src/main/res/values/themes.xml b/samples/kotlin-android-app/src/main/res/values/themes.xml new file mode 100644 index 00000000..a2b9cc47 --- /dev/null +++ b/samples/kotlin-android-app/src/main/res/values/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 6f6ab2b1..06e2a56e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -15,3 +15,5 @@ include 'android' project(':android').projectDir = file('android') include 'id' project(':id').projectDir = file('id') +include 'samples:kotlin-android-app' +project(':samples:kotlin-android-app').projectDir = file('samples/kotlin-android-app')