Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Improve user engagement with Google Play Store ...

Improve user engagement with Google Play Store Delivery

Talk given at .droidconGreece

Avatar for Donovan Fournier

Donovan Fournier

September 25, 2019

Other Decks in Technology

Transcript

  1. Application Library implementation apply plugin: 'com.android.application' dependencies { implementation "com.google.zxing:core:3.4.0"

    } // Application class com.google.zxing.MultiFormatWriter().encode( value, BarcodeFormat.QR_CODE, 500, 500, null ) apply plugin: 'com.android.library'
  2. Application Dynamic Feature implementation dynamicFeature apply plugin: 'com.android.dynamic-feature' dependencies {

    implementation project(':App') } apply plugin: 'com.android.application' android { dynamicFeatures = [":StockFeature"] }
  3. apply plugin: 'com.android.dynamic-feature' dependencies { implementation project(':App') } apply plugin:

    'com.android.application' android { dynamicFeatures = [":StockFeature"] } // MainActivity.kt StockEntrypoint.showActivity(context) Application MainActivity.kt Dynamic Feature (Stock Feature) StockEntrypoint.kt implementation dynamicFeature // MainActivity.kt StockEntrypoint.showActivity(context)
  4. Application MainActivity.kt Dynamic Feature (Stock Feature) StockEntrypoint.kt implementation dynamicFeature apply

    plugin: 'com.android.dynamic-feature' dependencies { implementation project(':App') } apply plugin: 'com.android.application' android { dynamicFeatures = [":StockFeature"] } // StockActivity.ky (this.application as MyApplication) .getDaggerComponent()
  5. Delivery mode Install Time OnDemand Device dependent <dist:module dist:title="@string/module_name_stock"> <dist:delivery>

    <dist:install-time /> </dist:delivery> </dist:module> <dist:module dist:title="@string/module_name_stock"> <dist:delivery> <dist:on-demand /> </dist:delivery> </dist:module> <dist:module dist:title="@string/module_name_payment"> <dist:delivery> <dist:install-time> <dist:conditions> <dist:device-feature dist:name="android.hardware.nfc.hce" /> </dist:conditions> </dist:install-time> </dist:delivery> </dist:module> CREATE YOUR 1st FEATURE
  6. WRITE FROM SCRATCH REFACTOR YOUR APP 1 2 Keep a

    single module Refactor in feature package Move your code Fix remaining errors Don’t move everything at the same time
  7. A NAVIGATION ISSUE // MainActivity.kt startActivity( Intent(this, StockActivity::class.java) ) //

    MainActivity.kt startActivity( Intent(this, StockActivity::class.java) ) Application Dynamic Feature me/dfournier/sample/MainActivity.kt (1 error) Unresolved reference: StockActivity
  8. enum class Feature(val entryClassName: String) { STOCK("$PACKAGE_NAME.stock.StockActivity") } fun Activity.startActivity(feature:

    Feature, vararg pairs: Pair<String, Any?>) { val intent = Intent(Intent.ACTION_VIEW) .setClassName(this.packageName, feature.entryClassName) .putExtras( bundleOf(*pairs) ) startActivity(intent) } const val PACKAGE_NAME = "me.dfournier.sample" const val EXTRA_SHARE_ID = "EXTRA_SHARE_ID" 1. Manual way Application Dynamic Feature A NAVIGATION ISSUE
  9. enum class Feature(val entryClassName: String) { STOCK("$PACKAGE_NAME.stock.StockActivity") } fun Activity.startActivity(feature:

    Feature, vararg pairs: Pair<String, Any?>) { val intent = Intent(Intent.ACTION_VIEW) .setClassName(this.packageName, feature.entryClassName) .putExtras( bundleOf(*pairs) ) startActivity(intent) } const val PACKAGE_NAME = "me.dfournier.sample" const val EXTRA_SHARE_ID = "EXTRA_SHARE_ID" 1. Manual way Application Dynamic Feature A NAVIGATION ISSUE
  10. enum class Feature(val entryClassName: String) { STOCK("$PACKAGE_NAME.stock.StockActivity") } fun Activity.startActivity(feature:

    Feature, vararg pairs: Pair<String, Any?>) { val intent = Intent(Intent.ACTION_VIEW) .setClassName(this.packageName, feature.entryClassName) .putExtras( bundleOf(*pairs) ) startActivity(intent) } const val PACKAGE_NAME = "me.dfournier.sample" const val EXTRA_SHARE_ID = "EXTRA_SHARE_ID" 1. Manual way Application Dynamic Feature A NAVIGATION ISSUE
  11. enum class Feature(val entryClassName: String) { STOCK("$PACKAGE_NAME.stock.StockActivity") } fun Activity.startActivity(feature:

    Feature, vararg pairs: Pair<String, Any?>) { val intent = Intent(Intent.ACTION_VIEW) .setClassName(this.packageName, feature.entryClassName) .putExtras( bundleOf(*pairs) ) startActivity(intent) } const val PACKAGE_NAME = "me.dfournier.sample" const val EXTRA_SHARE_ID = "EXTRA_SHARE_ID" 1. Manual way Application Dynamic Feature A NAVIGATION ISSUE
  12. enum class Feature(val entryClassName: String) { STOCK("$PACKAGE_NAME.stock.StockActivity") } fun Activity.startActivity(feature:

    Feature, vararg pairs: Pair<String, Any?>) { val intent = Intent(Intent.ACTION_VIEW) .setClassName(this.packageName, feature.entryClassName) .putExtras( bundleOf(*pairs) ) startActivity(intent) } const val PACKAGE_NAME = "me.dfournier.sample" const val EXTRA_SHARE_ID = "EXTRA_SHARE_ID" 1. Manual way Application Dynamic Feature A NAVIGATION ISSUE
  13. enum class Feature(val entryClassName: String) { STOCK("$PACKAGE_NAME.stock.StockActivity") } fun Activity.startActivity(feature:

    Feature, vararg pairs: Pair<String, Any?>) { val intent = Intent(Intent.ACTION_VIEW) .setClassName(this.packageName, feature.entryClassName) .putExtras( bundleOf(*pairs) ) startActivity(intent) } const val PACKAGE_NAME = "me.dfournier.sample" const val EXTRA_SHARE_ID = "EXTRA_SHARE_ID" 1. Manual way Application Dynamic Feature A NAVIGATION ISSUE
  14. <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.DEFAULT" />

    <data android:host="sample.dfournier.me" /> <data android:pathPattern="/stock/share/.*" /> <data android:scheme="https" /> <data android:scheme="http" /> </intent-filter> 1. Manual way Application Dynamic Feature 2. URI based A NAVIGATION ISSUE
  15. MANAGE YOUR FEATURE val splitInstallManager: SplitInstallManager = SplitInstallManagerFactory.create(this) val moduleName

    = "stockfeature" if (moduleName in splitInstallManager.installedModules) { startFeature(moduleName) } else { val request = SplitInstallRequest.newBuilder() .addModule(moduleName) .build() splitInstallManager.startInstall(request) .addOnSuccessListener { sessionId -> Timber.d("addOnSuccessListener") // Track the progress } .addOnCompleteListener { Timber.d("addOnCompleteListener") } .addOnFailureListener { Timber.e(it, "addOnFailureListener") } }
  16. MANAGE YOUR FEATURE val splitInstallManager: SplitInstallManager = SplitInstallManagerFactory.create(this) val moduleName

    = "stockfeature" if (moduleName in splitInstallManager.installedModules) { startFeature(moduleName) } else { val request = SplitInstallRequest.newBuilder() .addModule(moduleName) .build() splitInstallManager.startInstall(request) .addOnSuccessListener { sessionId -> Timber.d("addOnSuccessListener") // Track the progress } .addOnCompleteListener { Timber.d("addOnCompleteListener") } .addOnFailureListener { Timber.e(it, "addOnFailureListener") } }
  17. MANAGE YOUR FEATURE val splitInstallManager: SplitInstallManager = SplitInstallManagerFactory.create(this) val moduleName

    = "stockfeature" if (moduleName in splitInstallManager.installedModules) { startFeature(moduleName) } else { val request = SplitInstallRequest.newBuilder() .addModule(moduleName) .build() splitInstallManager.startInstall(request) .addOnSuccessListener { sessionId -> Timber.d("addOnSuccessListener") // Track the progress } .addOnCompleteListener { Timber.d("addOnCompleteListener") } .addOnFailureListener { Timber.e(it, "addOnFailureListener") } }
  18. MANAGE YOUR FEATURE val splitInstallManager: SplitInstallManager = SplitInstallManagerFactory.create(this) val moduleName

    = "stockfeature" if (moduleName in splitInstallManager.installedModules) { startFeature(moduleName) } else { val request = SplitInstallRequest.newBuilder() .addModule(moduleName) .build() splitInstallManager.startInstall(request) .addOnSuccessListener { sessionId -> Timber.d("addOnSuccessListener") // Track the progress } .addOnCompleteListener { Timber.d("addOnCompleteListener") } .addOnFailureListener { Timber.e(it, "addOnFailureListener") } }
  19. MANAGE YOUR FEATURE val splitInstallManager: SplitInstallManager = SplitInstallManagerFactory.create(this) val moduleName

    = "stockfeature" if (moduleName in splitInstallManager.installedModules) { startFeature(moduleName) } else { val request = SplitInstallRequest.newBuilder() .addModule(moduleName) .build() splitInstallManager.startInstall(request) .addOnSuccessListener { sessionId -> Timber.d("addOnSuccessListener") // Track the progress } .addOnCompleteListener { Timber.d("addOnCompleteListener") } .addOnFailureListener { Timber.e(it, "addOnFailureListener") } }
  20. MANAGE YOUR FEATURE val splitInstallManager: SplitInstallManager = SplitInstallManagerFactory.create(this) val moduleName

    = "stockfeature" if (moduleName in splitInstallManager.installedModules) { startFeature(moduleName) } else { val request = SplitInstallRequest.newBuilder() .addModule(moduleName) .build() splitInstallManager.startInstall(request) .addOnSuccessListener { sessionId -> Timber.d("addOnSuccessListener") // Track the progress } .addOnCompleteListener { Timber.d("addOnCompleteListener") } .addOnFailureListener { Timber.e(it, "addOnFailureListener") } }
  21. fun onStateUpdate(state: SplitInstallSessionState) // state.status() // SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION splitInstallManager.startConfirmationDialogForResult( splitInstallSessionState, this,

    0 ) // SplitInstallSessionStatus.FAILED // SplitInstallSessionStatus.DOWNLOADING // SplitInstallSessionStatus.INSTALLED SplitInstallHelper.updateAppInfo(this@MainActivity) // Override in your application override fun attachBaseContext(base: Context) { super.attachBaseContext(base) if (!InstantApps.isInstantApp(this)) { SplitCompat.install(this) } } MANAGE YOUR FEATURE
  22. fun onStateUpdate(state: SplitInstallSessionState) // state.status() // SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION splitInstallManager.startConfirmationDialogForResult( splitInstallSessionState, this,

    0 ) // SplitInstallSessionStatus.FAILED // SplitInstallSessionStatus.DOWNLOADING // SplitInstallSessionStatus.INSTALLED SplitInstallHelper.updateAppInfo(this@MainActivity) // Override in your application override fun attachBaseContext(base: Context) { super.attachBaseContext(base) if (!InstantApps.isInstantApp(this)) { SplitCompat.install(this) } } MANAGE YOUR FEATURE
  23. fun onStateUpdate(state: SplitInstallSessionState) // state.status() // SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION splitInstallManager.startConfirmationDialogForResult( splitInstallSessionState, this,

    0 ) // SplitInstallSessionStatus.FAILED // SplitInstallSessionStatus.DOWNLOADING // SplitInstallSessionStatus.INSTALLED SplitInstallHelper.updateAppInfo(this@MainActivity) // Override in your application override fun attachBaseContext(base: Context) { super.attachBaseContext(base) if (!InstantApps.isInstantApp(this)) { SplitCompat.install(this) } } MANAGE YOUR FEATURE
  24. fun onStateUpdate(state: SplitInstallSessionState) // state.status() // SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION splitInstallManager.startConfirmationDialogForResult( splitInstallSessionState, this,

    0 ) // SplitInstallSessionStatus.FAILED // SplitInstallSessionStatus.DOWNLOADING // SplitInstallSessionStatus.INSTALLED SplitInstallHelper.updateAppInfo(this@MainActivity) // Override in your application override fun attachBaseContext(base: Context) { super.attachBaseContext(base) if (!InstantApps.isInstantApp(this)) { SplitCompat.install(this) } } MANAGE YOUR FEATURE
  25. fun onStateUpdate(state: SplitInstallSessionState) // state.status() // SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION splitInstallManager.startConfirmationDialogForResult( splitInstallSessionState, this,

    0 ) // SplitInstallSessionStatus.FAILED // SplitInstallSessionStatus.DOWNLOADING // SplitInstallSessionStatus.INSTALLED SplitInstallHelper.updateAppInfo(this@MainActivity) // Override in your application override fun attachBaseContext(base: Context) { super.attachBaseContext(base) if (!InstantApps.isInstantApp(this)) { SplitCompat.install(this) } } MANAGE YOUR FEATURE
  26. fun onStateUpdate(state: SplitInstallSessionState) // state.status() // SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION splitInstallManager.startConfirmationDialogForResult( splitInstallSessionState, this,

    0 ) // SplitInstallSessionStatus.FAILED // SplitInstallSessionStatus.DOWNLOADING // SplitInstallSessionStatus.INSTALLED SplitInstallHelper.updateAppInfo(this@MainActivity) // Override in your application override fun attachBaseContext(base: Context) { super.attachBaseContext(base) if (!InstantApps.isInstantApp(this)) { SplitCompat.install(this) } } MANAGE YOUR FEATURE
  27. o API vs Implementation o Module Name (And obfuscation) o

    AttachContext Issue o Resource Name Overlap TIPS
  28. o One time usage o No installation process o Accessible

    via - “Try now” on the Play Store - HTTP(s) links INSTANT APPS Limited access to device Secure network request only Google requirements Android 5.0
  29. MAKE YOUR APP INSTANT Try Now apply plugin: 'com.android.application’ <dist:module

    dist:instant="true" /> 1- Inside your Android module 2- Declare your App as instant
  30. MAKE YOUR APP INSTANT Try Now apply plugin: 'com.android.application’ <dist:module

    dist:instant="true" /> <meta-data android:name="default-url" android:value="{{URL}}" /> 1- Inside your Android module 2- Declare your App as instant 3- Declare the main entrypoint
  31. MAKE YOUR APP INSTANT Through a link 1- Declare your

    deep link <intent-filter android:autoVerify="true"> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.DEFAULT" /> <data android:host="sample.dfournier.me" /> <data android:pathPattern="/stock/share/.*" /> <data android:scheme="https" /> <data android:scheme="http" /> </intent-filter>
  32. MAKE YOUR APP INSTANT Through a link 1- Declare your

    deep link 2- Authentify your app on your domain <intent-filter android:autoVerify="true"> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.DEFAULT" /> <data android:host="sample.dfournier.me" /> <data android:pathPattern="/stock/share/.*" /> <data android:scheme="https" /> <data android:scheme="http" /> </intent-filter> https://{{YourDomain}}/.well-known/assetlinks.json [ { "relation": [ "delegate_permission/common.handle_all_urls" ], "target": { "namespace": "android_app", "package_name": "me.dfournier.sample", "sha256_cert_fingerprints": [ "65:7E:58:05:6B:A7:59…" ] } } ]
  33. <intent-filter android:autoVerify="true"> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.DEFAULT"

    /> <data android:host="sample.dfournier.me" /> <data android:pathPattern="/stock/share/.*" /> <data android:scheme="https" /> <data android:scheme="http" /> </intent-filter> INSTANT DYNAMIC FEATURE ?
  34. <intent-filter android:autoVerify="true"> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.DEFAULT"

    /> <data android:host="sample.dfournier.me" /> <data android:pathPattern="/stock/share/.*" /> <data android:scheme="https" /> <data android:scheme="http" /> </intent-filter> INSTANT DYNAMIC FEATURE ?
  35. INSTANT DYNAMIC FEATURE ! A feature cannot be instant and

    on-demand <dist:module dist:instant="true" dist:title="@string/module_title_subscribe"> <dist:delivery> <dist:install-time /> </dist:delivery> </dist:module>
  36. INSTANT APPS UX InstantApps.getPackageManagerCompat(context).isInstantApp o Adapt the UI for Instant

    Experience o Suggest the installation o Persist user setting through launch / installation o The user may encounter a confirmation window o Use Google SmartLock for authentication fun showInstallPrompt( activity: Activity, postInstallIntent: Intent, requestCode: Int, referrer: String?): Boolean
  37. o Get your SHA on the Play Console o Manage

    environment with variants and manifestPlaceholders o Declare several app on your domain o Keep track of unused resources & APK size o Extract large data into another feature module TIPS
  38. Google Play Console Android Studio TEST & DISTRIBUTE ia run

    [-s=<deviceSerial>] [-u=<url>] [<appArtifact>...] ia check [-i=<installableApk>] <appArtifact>...