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

Hidden gems and wats for the Modern android Dev...

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

Hidden gems and wats for the Modern android Developer

Talk was given at Android Madg (https://youtu.be/p9-LAdDpudw?t=4151)

Avatar for Saul Molinero

Saul Molinero

November 25, 2020
Tweet

More Decks by Saul Molinero

Other Decks in Programming

Transcript

  1. @_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
  2. @_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)
  3. @_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)
  4. @_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
  5. @_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)
  6. @_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 ) {
  7. @_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>
  8. @_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>
  9. @_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>
  10. @_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 ) {
  11. @_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 ) {
  12. @_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 ) {
  13. @_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 ) {
  14. @_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 ) {
  15. @_saulmm class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?)

    { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } }
  16. @_saulmm class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?)

    { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } }
  17. @_saulmm class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?,

    persistentState: PersistableBundle?) { super.onCreate(savedInstanceState, persistentState) setContentView(R.layout.activity_main) } }
  18. @_saulmm class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?,

    persistentState: PersistableBundle?) { super.onCreate(savedInstanceState, persistentState) setContentView(R.layout.activity_main) } }
  19. @_saulmm class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?,

    persistentState: PersistableBundle?) { super.onCreate(savedInstanceState, persistentState) setContentView(R.layout.activity_main) } }
  20. @_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
  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])
  22. @_saulmm class UserProfileActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContentView(R.layout.activity_user_profile) } }
  23. @_saulmm class UserProfileActivity: AppCompatActivity( R.layout.activity_user_profile ) { / / override

    fun onCreate(savedInstanceState: Bundle?) { / / super.onCreate(savedInstanceState) / / setContentView(R.layout.activity_user_profile) / / } }
  24. @_saulmm viewBinding class UserProfileActivity: AppCompatActivity() { lateinit var binding: ActivityUserProfileBinding

    override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityUserProfileBinding.inflate(layoutInflater) setContentView(binding.root) } }
  25. @_saulmm viewBinding class UserProfileActivity: AppCompatActivity() { private val binding by

    viewBinding( ActivityRequestPhoneBinding : : inflate ) override fun onCreate(savedInstanceState: Bundle?) { super.setContentView(savedInstanceState) setContentView(binding.root) } }
  26. @_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
  27. @_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
  28. @_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
  29. @_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
  30. @_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
  31. @_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
  32. @_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
  33. @_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
  34. @_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
  35. @_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 }
  36. @_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); }
  37. @_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: 

  38. @_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” />
  39. @_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" } ] }
  40. @_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” },
  41. @_saulmm Generate Proguard Rules APK Analyzer can help you see

    which classes were removed by ProGuard and generate keep rules for them.
  42. @_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")
  43. @_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
  44. @_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) {
  45. @_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) {
  46. @_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
  47. @_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
  48. @_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
  49. @_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
  50. @_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
  51. @_saulmm Enums in Android “Think of it this way, enums

    
 are kind of like gremlins, right?” 
 
 “The price of ENUMS” 3:10 

  52. @_saulmm Enums in Android “Think of it this way, enums

    
 are kind of like gremlins, right?” 
 
 “The price of ENUMS” 3:10 

  53. @_saulmm Enums in Android Proguard and R8 can optimise simple

    Java and Kotlin enums into ints for you
  54. @_saulmm Enums in Android -optimizations class/unboxing/enum Proguard and R8 can

    optimise simple Java and Kotlin enums into ints for you