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

Hidden gems and wats for the Modern android Developer

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
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