Share
Embed
Hidden gems and wats for the Modern android Developer
Talk was given at Android Madg (https://youtu.be/p9-LAdDpudw?t=4151)
Saul Molinero
November 25, 2020
Transcript
109 slides
-
@_saulmm android.R.string <TextView android:text="@android:string/no" /> <TextView android:text=“@android:string/yes" /> <TextView android:text=“@android:string/cancel”
/> <TextView android:text=“@android:string/ok” /> -
@_saulmm Entities <resources> <string name="app_name">Repunantiño</string> <string name="txt_selection">%1$d selected items</string> <string
name="txt_feedback">Not happy with Repunantiño? support: [email protected]</string> </resources> -
@_saulmm Entities <resources> <!ENTITY app_name "Repunantiño"> <!ENTITY support_mail "[email protected]"> ]>
&app_name; &support_mail; &app_name; -
@_saulmm Bill of Materials (BOM) / / Firebase
implementation platform(“com.google.firebase:firebase-bom:26.1.0") implementation "com.google.firebase:firebase-analytics" implementation "com.google.firebase:firebase-perf" implementation "com.google.firebase:firebase-messaging" implementation "com.google.firebase:firebase-config-ktx" implementation "com.google.firebase:firebase-appindexing" implementation 'com.google.firebase:firebase-inappmessaging-display' implementation 'com.google.firebase:firebase-storage' implementation 'com.google.firebase:firebase-crashlytics-ktx' https://firebase.google.com/docs/android/learn-more#compare-bom-versions -
@_saulmm / / Firebase implementation platform(“com.google.firebase:firebase-bom:26.1.0") implementation
"com.google.firebase:firebase-analytics" implementation "com.google.firebase:firebase-perf" implementation "com.google.firebase:firebase-messaging" implementation "com.google.firebase:firebase-config-ktx" implementation "com.google.firebase:firebase-appindexing" implementation 'com.google.firebase:firebase-inappmessaging-display' implementation 'com.google.firebase:firebase-storage' implementation 'com.google.firebase:firebase-crashlytics-ktx' https://firebase.google.com/docs/android/learn-more#compare-bom-versions Bill of Materials (BOM) -
@_saulmm / / Firebase implementation “com.google.firebase:firebase-analytics:17.5.0” implementation “com.google.firebase:firebase-perf:19.0.8” implementation “com.google.firebase:firebase-messaging:20.2.4”
implementation “com.google.firebase:firebase-config-ktx:19.2.0” implementation “com.google.firebase:firebase-appindexing:17.0.0” implementation ‘com.google.firebase:firebase-inappmessaging-display:19.1.0’ implementation ‘com.google.firebase:firebase-storage:19.1.1’ implementation ‘com.google.firebase:firebase-crashlytics-ktx:17.2.2’ https://firebase.google.com/docs/android/learn-more#compare-bom-versions Bill of Materials (BOM) -
@_saulmm Bill of Materials (BOM) / / Firebase
implementation platform(“com.google.firebase:firebase-bom:26.1.0") implementation "com.google.firebase:firebase-analytics" implementation "com.google.firebase:firebase-perf" implementation "com.google.firebase:firebase-messaging" implementation “com.google.firebase:firebase-config-ktx:19.2.0” implementation "com.google.firebase:firebase-appindexing" implementation 'com.google.firebase:firebase-inappmessaging-display' implementation 'com.google.firebase:firebase-storage' implementation 'com.google.firebase:firebase-crashlytics-ktx' > https://firebase.google.com/docs/android/learn-more#compare-bom-versions -
@_saulmm #Fri Oct 16 11:09:31 CEST 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists distributionUrl=https\: / / services.gradle.org/distributions/gradle-6.5-all.zip Project/gradle/wrapper/gradle-wrapper.properties > >= 5 Bill of Materials (BOM) -
@_saulmm import android.content.Context import android.text.SpannableString import android.text.style.ForegroundColorSpan import android.view.View import
android.widget.PopupMenu class QuickMessagesPopup( private val context: Context, anchor: View, ) : PopupMenu( context, anchor ) { -
@_saulmm <style name="PopsyTheme" parent="Base"> < ! - - . .
. - - > <item name="textAppearanceSmallPopupMenu">@style/TextAppearance.Popsy.PopupMenu.Small < / item> <item name="textAppearanceLargePopupMenu">@style/TextAppearance.Popsy.PopupMenu.Large < / item> < ! - - . . . - - > < / style> <style name="TextAppearance.Popsy.PopupMenu.Large" parent="@style/TextAppearance.Popsy.Body2.Dark"> <item name=“android:textColor">?colorOnSurface < / item> <item name="android:textSize">14sp < / item> < / style> <style name="TextAppearance.Popsy.PopupMenu.Small" parent="@style/TextAppearance.Popsy.Body2.Dark"> <item name=“android:textColor">?colorOnSurface < / item> <item name="android:textSize">12sp < / item> < / style> -
@_saulmm <style name="PopsyTheme" parent="Base"> < ! - - . .
. - - > <item name="textAppearanceSmallPopupMenu">@style/TextAppearance.Popsy.PopupMenu.Small < / item> <item name="textAppearanceLargePopupMenu">@style/TextAppearance.Popsy.PopupMenu.Large < / item> < ! - - . . . - - > < / style> <style name="TextAppearance.Popsy.PopupMenu.Large" parent="@style/TextAppearance.Popsy.Body2.Dark"> <item name="android:textColor">@color/red_700 < / item> <item name="android:textSize">14sp < / item> < / style> <style name="TextAppearance.Popsy.PopupMenu.Small" parent="@style/TextAppearance.Popsy.Body2.Dark"> <item name="android:textColor">@color/red_700 < / item> <item name="android:textSize">12sp < / item> < / style> -
@_saulmm <style name="PopsyTheme" parent="Base"> < ! - - . .
. - - > <item name="textAppearanceSmallPopupMenu">@style/TextAppearance.Popsy.PopupMenu.Small < / item> <item name="textAppearanceLargePopupMenu">@style/TextAppearance.Popsy.PopupMenu.Large < / item> < ! - - . . . - - > < / style> <style name="TextAppearance.Popsy.PopupMenu.Large" parent="@style/TextAppearance.Popsy.Body2.Dark"> <item name="android:textColor">@color/red_700 < / item> <item name="android:textSize">14sp < / item> < / style> <style name="TextAppearance.Popsy.PopupMenu.Small" parent="@style/TextAppearance.Popsy.Body2.Dark"> <item name="android:textColor">@color/red_700 < / item> <item name="android:textSize">12sp < / item> < / style> -
@_saulmm import android.content.Context import android.text.SpannableString import android.text.style.ForegroundColorSpan import android.view.View import
android.widget.PopupMenu class QuickMessagesPopup( private val context: Context, anchor: View, ) : PopupMenu( context, anchor ) { -
@_saulmm import android.content.Context import android.text.SpannableString import android.text.style.ForegroundColorSpan import android.view.View import
android.widget.PopupMenu class QuickMessagesPopup( private val context: Context, anchor: View, ) : PopupMenu( context, anchor ) { -
@_saulmm import android.content.Context import android.text.SpannableString import android.text.style.ForegroundColorSpan import android.view.View import
androidx.appcompat.widget.PopupMenu class QuickMessagesPopup( private val context: Context, anchor: View, ) : PopupMenu( context, anchor ) { -
@_saulmm import android.content.Context import android.text.SpannableString import android.text.style.ForegroundColorSpan import android.view.View import
androidx.appcompat.widget.PopupMenu class QuickMessagesPopup( private val context: Context, anchor: View, ) : PopupMenu( context, anchor ) { -
@_saulmm import android.content.Context import android.text.SpannableString import android.text.style.ForegroundColorSpan import android.view.View import
androidx.appcompat.widget.PopupMenu class QuickMessagesPopup( private val context: Context, anchor: View, ) : PopupMenu( context, anchor ) { -
@_saulmm class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?)
{ super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } } -
@_saulmm class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?)
{ super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } } -
@_saulmm class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?,
persistentState: PersistableBundle?) { super.onCreate(savedInstanceState, persistentState) setContentView(R.layout.activity_main) } } -
@_saulmm class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?,
persistentState: PersistableBundle?) { super.onCreate(savedInstanceState, persistentState) setContentView(R.layout.activity_main) } } -
@_saulmm class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?,
persistentState: PersistableBundle?) { super.onCreate(savedInstanceState, persistentState) setContentView(R.layout.activity_main) } } -
@_saulmm How to obtain a vector ContextCompat.getDrawable(…) ResourcesCompat.getDrawable(…) AppCompatResources.getDrawable(…) VectorDrawableCompat.create(…)
android.defaultConfig. vectorDrawables. useSupportLibrary = true. ContextCompat.getDrawable(…) ResourcesCompat.getDrawable(…) AppCompatResources.getDrawable(…) VectorDrawableCompat.create(…) API >= 21 API <= 21 -
@_saulmm Go to source Log.i("IMPORTANT", "look at my java file
.(Hello.java:6)") Log.i("IMPORTANT", "For kotlin too! (MainActivity.kt:20)") .([filename].java:[line]) ([filename].kt:[line]) -
@_saulmm Go to source Log.i("IMPORTANT", "look at my java file
.(Hello.java:6)") -
@_saulmm class UserProfileActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) setContentView(R.layout.activity_user_profile) } } -
@_saulmm class UserProfileActivity: AppCompatActivity( R.layout.activity_user_profile ) { / / override
fun onCreate(savedInstanceState: Bundle?) { / / super.onCreate(savedInstanceState) / / setContentView(R.layout.activity_user_profile) / / } } -
@_saulmm viewBinding class UserProfileActivity: AppCompatActivity() { lateinit var binding: ActivityUserProfileBinding
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityUserProfileBinding.inflate(layoutInflater) setContentView(binding.root) } } -
@_saulmm viewBinding class UserProfileActivity: AppCompatActivity() { private val binding by
viewBinding( ActivityRequestPhoneBinding : : inflate ) override fun onCreate(savedInstanceState: Bundle?) { super.setContentView(savedInstanceState) setContentView(binding.root) } } -
@_saulmm viewBinding inline fun <T : ViewBinding> AppCompatActivity.viewBinding( crossinline bindingInflater:
(LayoutInflater) - > T ): Lazy<T> { return lazy(LazyThreadSafetyMode.NONE) { bindingInflater.invoke(layoutInflater) } } https://medium.com/@Zhuinden/simple-one-liner-viewbinding-in-fragments-and-activities-with-kotlin-961430c6c07c -
@_saulmm viewBinding class UserProfileFragment: Fragment() { override fun onCreateView( inflater:
LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { super.onCreateView(inflater, container, savedInstanceState) val v = inflater.inflate(fragment_profile, container, false) return v } } https://medium.com/@Zhuinden/simple-one-liner-viewbinding-in-fragments-and-activities-with-kotlin-961430c6c07c -
@_saulmm viewBinding class UserProfileFragment: Fragment() { lateinit var binding: ActivityUserProfileBinding
override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { super.onCreateView(inflater, container, savedInstanceState) binding = FragmentUserProfileBinding.inflate(container, false) return binding.root } } https://medium.com/@Zhuinden/simple-one-liner-viewbinding-in-fragments-and-activities-with-kotlin-961430c6c07c -
@_saulmm viewBinding class UserProfileFragment: Fragment(R.layout.fragment_user_profile) { lateinit var binding: FragmentUserProfileBinding
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding = FragmentUserProfileBinding.bind(view) } } https://medium.com/@Zhuinden/simple-one-liner-viewbinding-in-fragments-and-activities-with-kotlin-961430c6c07c -
@_saulmm viewBinding class UserProfileFragment: Fragment(R.layout.activity_user_profile) { private val binding by
viewBinding(FragmentUserProfileBinding : : bind) } https://medium.com/@Zhuinden/simple-one-liner-viewbinding-in-fragments-and-activities-with-kotlin-961430c6c07c -
@_saulmm viewBinding class FragmentViewBindingDelegate<T : ViewBinding>( val fragment: Fragment, val
viewBindingFactory: (View) - > T ) : ReadOnlyProperty<Fragment, T> { private var binding: T? = null init { fragment.lifecycle.addObserver(object : DefaultLifecycleObserver { override fun onCreate(owner: LifecycleOwner) { fragment.viewLifecycleOwnerLiveData.observe(fragment) { viewLifecycleOwner - > viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver { override fun onDestroy(owner: LifecycleOwner) { binding = null } }) } } }) } override fun getValue(thisRef: Fragment, property: KProperty < * > ): T { val binding = binding if (binding ! = null) { return binding } val lifecycle = fragment.viewLifecycleOwner.lifecycle if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) { throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed.") } return viewBindingFactory(thisRef.requireView()).also { this.binding = it } } } https://medium.com/@Zhuinden/simple-one-liner-viewbinding-in-fragments-and-activities-with-kotlin-961430c6c07c -
@_saulmm viewBinding class FragmentViewBindingDelegate<T : ViewBinding>( val fragment: Fragment, val
viewBindingFactory: (View) - > T ) : ReadOnlyProperty<Fragment, T> { private var binding: T? = null init { fragment.lifecycle.addObserver(object : DefaultLifecycleObserver { override fun onCreate(owner: LifecycleOwner) { fragment.viewLifecycleOwnerLiveData.observe(fragment) { viewLifecycleOwner - > viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver { override fun onDestroy(owner: LifecycleOwner) { binding = null } }) } } }) } override fun getValue(thisRef: Fragment, property: KProperty < * > ): T { val binding = binding if (binding ! = null) { return binding } val lifecycle = fragment.viewLifecycleOwner.lifecycle if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) { throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed.") } return viewBindingFactory(thisRef.requireView()).also { this.binding = it } } } https://medium.com/@Zhuinden/simple-one-liner-viewbinding-in-fragments-and-activities-with-kotlin-961430c6c07c -
@_saulmm viewBinding class FragmentViewBindingDelegate<T : ViewBinding>( val fragment: Fragment, val
viewBindingFactory: (View) - > T ) : ReadOnlyProperty<Fragment, T> { private var binding: T? = null init { fragment.lifecycle.addObserver(object : DefaultLifecycleObserver { override fun onCreate(owner: LifecycleOwner) { fragment.viewLifecycleOwnerLiveData.observe(fragment) { viewLifecycleOwner - > viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver { override fun onDestroy(owner: LifecycleOwner) { binding = null } }) } } }) } override fun getValue(thisRef: Fragment, property: KProperty < * > ): T { val binding = binding if (binding ! = null) { return binding } val lifecycle = fragment.viewLifecycleOwner.lifecycle if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) { throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed.") } return viewBindingFactory(thisRef.requireView()).also { this.binding = it } } } https://medium.com/@Zhuinden/simple-one-liner-viewbinding-in-fragments-and-activities-with-kotlin-961430c6c07c -
@_saulmm binding.recyclerNotifications.isVisible = !isEmpty /** * Setting this property to
true sets the visibility to * [View.VISIBLE], false to [View.GONE]. **/ inline var View.isVisible: Boolean get() = visibility == View.VISIBLE set(value) { visibility = if (value) View.VISIBLE else View.GONE } View.isVisible -
@_saulmm View.isVisible binding.recyclerNotifications.isVisible = !isEmpty /** * Setting this property
to true sets the visibility to * [View.VISIBLE], false to [View.GONE]. **/ inline var View.isVisible: Boolean get() = visibility == View.VISIBLE set(value) { visibility = if (value) View.VISIBLE else View.GONE } -
@_saulmm beginDelayedTransition() package android.transition; /** * Convenience method to
animate, using the default transition, * to a new scene defined by all changes within the given scene root between * calling this method and the next rendering frame. */ public static void beginDelayedTransition(final ViewGroup sceneRoot) { beginDelayedTransition(sceneRoot, null); } -
@_saulmm ViewStub “If you have parts of your UI that
do not need to be visible on f i rst launch, don’t in f l ate them” Developing for Android, III: -
@_saulmm ViewStub private fun onNetworkError() { val view = binding.stubNetwork
.viewStub?.inflate() } -
@_saulmm @tools:sample <TextView android:id="@+id/txt_content" tools:text="@tools:sample/lorem/random" /> <TextView android:id=“@+id/txt_subtitle" tools:text=“@tools:sample/first_names” />
<TextView android:id="@+id/txt_content" tools:text=“@tools:sample/date/hhmm" /> <ImageView android:id=“@+id/image_secondary” tools:src=“@tools:sample/avatars” /> <ImageView android:id=“@+id/img_secondary” tools:src=“@tools:sample/avatars” /> -
@_saulmm @tools:sample app/sampledata/listings.json { "data": [ { "listing": "Brand new
iPhone" }, { "listing": "Garming 1030 new" }, { "listing": "Toyota Yaris 1.5 hdi 100km" }, { "listing": "Google Home Mini" }, { "listing": "Parrot toy" }, { "listing": "Lawn mower" }, { "listing": "Moving sale" }, { "listing": "Porter cable tools" }, { "listing": "Workout set" } ] } -
@_saulmm @tools:sample <TextView android:id=“@+id/txt_status_title” tools:text=“@tools:listings.json/data/listing” /> <TextView android:id=“@+id/txt_header” tools:text=“@tools:deliveries.json/data/status” />
<TextView android:id=“@+id/txt_status_info” tools:text=“@tools:deliveries.json/data/status_short” /> -
@_saulmm @tools:sample <TextView "data": [ { "status": “@string/title_status_unconfirmed”, "status_short" :
“@string/msg_status_unconfirmed" }, { "status": “@string/title_status_confirmed, "status_short" : “@string/msg_status_confirmed” }, -
@_saulmm Generate Proguard Rules APK Analyzer can help you see
which classes were removed by ProGuard and generate keep rules for them. -
@_saulmm FragmentContainerView <androidx.fragment.app.FragmentContainerView android:id="@+id/container" android:name=“myapp.MyFragment” android:layout_width="match_parent" android:layout_height="wrap_content" /> - Improvements
on entering / exiting animations - Allow replacing <fragment ../> -
@_saulmm FragmentContainerView <androidx.fragment.app.FragmentContainerView android:id="@+id/container" android:name=“myapp.MyFragment” android:layout_width="match_parent" android:layout_height="wrap_content" /> - Improvements
on entering / exiting animations - Allow replacing <fragment ../> -
@_saulmm FragmentContainerView <androidx.fragment.app.FragmentContainerView android:id="@+id/container" android:name=“myapp.MyFragment” android:layout_width="match_parent" android:layout_height="wrap_content" /> - Improvements
on entering / exiting animations - Allow replacing <fragment ../> -
@_saulmm Log.i(TAG, "An information log") Log.d(TAG, "A debug log") Log.v(TAG,
"A verbose log") Log.e(TAG, "An error log") Log.w(TAG, "A Warning log") -
@_saulmm Log.i(TAG, "An information log") 2019-12-12 10:55:55.165 D/yea debug log
2019-12-12 10:55:55.165 V/yea: A verbose log 2019-12-12 10:55:55.165 E/yea: An error log 2019-12-12 10:55:55.165 W/yea: A warning log -
@_saulmm /** * What a Terrible Failure: Report a condition
that should never happen. * The error will always be logged at level ASSERT with the call stack. * Depending on system configuration, a report may be added to the * {@link android.os.DropBoxManager} and/or the process may be terminated * immediately with an error dialog. * @param tag Used to identify the source of a log message. * @param msg The message you would like logged. */ public static int wtf(String tag, String msg) { -
@_saulmm /** * What a Terrible Failure: Report a condition
that should never happen. * The error will always be logged at level ASSERT with the call stack. * Depending on system configuration, a report may be added to the * {@link android.os.DropBoxManager} and/or the process may be terminated * immediately with an error dialog. * @param tag Used to identify the source of a log message. * @param msg The message you would like logged. */ public static int wtf(String tag, String msg) { -
@_saulmm Selection Tracker dependencies { // ... implementation 'androidx.recyclerview:recyclerview-selection:1.0.0' }
https://proandroiddev.com/a-guide-to-recyclerview-selection-3ed9f2381504 -
@_saulmm Selection Tracker class PoiAdapter: RecyclerView.Adapter<PoiViewHolder>() { MainActivity.PO_SELECTION_KEY, recyclerView, StableIdKeyProvider(recyclerView),
PoiDetailsLookUp(recyclerView), StorageStrategy.createLongStorage() ).build() lateinit var tracker: SelectionTracker<Long> https://proandroiddev.com/a-guide-to-recyclerview-selection-3ed9f2381504 -
@_saulmm Selection Tracker class PoiDetailsLookUp(val recyclerView: RecyclerView) : ItemDetailsLookup<Long>() {
override fun getItemDetails(e: MotionEvent): ItemDetails<Long>? { val view = recyclerView.findChildViewUnder(e.x, e.y) return if (view != null) { (recyclerView.getChildViewHolder(view) as PoiViewHolder) .itemDetails() } else { null } } } https://proandroiddev.com/a-guide-to-recyclerview-selection-3ed9f2381504 -
@_saulmm Selection Tracker class PoiViewHolder(val binding: ItemRowBinding): RecyclerView.ViewHolder(binding.root) {
fun bind(item: Poi, isActivated: Boolean) { binding.poi = item binding.root.isActivated = isActivated } fun itemDetails(): ItemDetailsLookup.ItemDetails<Long> = object : ItemDetailsLookup.ItemDetails<Long>() { override fun getSelectionKey(): Long? = itemId override fun getPosition(): Int = adapterPosition } } https://proandroiddev.com/a-guide-to-recyclerview-selection-3ed9f2381504 -
@_saulmm Selection Tracker class PoiViewHolder(val binding: ItemRowBinding): RecyclerView.ViewHolder(binding.root) {
fun bind(item: Poi, isActivated: Boolean) { binding.poi = item binding.root.isActivated = isActivated } fun itemDetails(): ItemDetailsLookup.ItemDetails<Long> = object : ItemDetailsLookup.ItemDetails<Long>() { override fun getSelectionKey(): Long? = itemId override fun getPosition(): Int = adapterPosition } } https://proandroiddev.com/a-guide-to-recyclerview-selection-3ed9f2381504 -
@_saulmm Selection Tracker fun addSelectionObserver(selectionObserver : SelectionTracker.SelectionObserver<Long>) { tracker.addObserver(selectionObserver)
} poiAdapter.addSelectionObserver(object : SelectionTracker.SelectionObserver<Long>() { override fun onSelectionChanged() { super.onSelectionChanged() val count = poiAdapter.tracker.selection.size() val hasSelection = !poiAdapter.tracker.selection.isEmpty // Start action mode here } }) https://proandroiddev.com/a-guide-to-recyclerview-selection-3ed9f2381504 -
@_saulmm Enums in Android “Think of it this way, enums
are kind of like gremlins, right?” “The price of ENUMS” 3:10 -
@_saulmm Enums in Android “Think of it this way, enums
are kind of like gremlins, right?” “The price of ENUMS” 3:10 -
@_saulmm Enums in Android Proguard and R8 can optimise simple
Java and Kotlin enums into ints for you -
@_saulmm Enums in Android -optimizations class/unboxing/enum Proguard and R8 can
optimise simple Java and Kotlin enums into ints for you