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

Android KTX, DataBinding으로 코드 생산성 개선하기

Sungyong An
December 08, 2019

Android KTX, DataBinding으로 코드 생산성 개선하기

At DevFest Android 2019

Sungyong An

December 08, 2019
Tweet

More Decks by Sungyong An

Other Decks in Programming

Transcript

  1. // Kotlin loadingView .visibility = if (isLoading) View .VISIBLE else

    View.GONE // Java loadingView.setVisibility( isLoading ? View.VISIBLE : View.GONE);
  2. Android KTX Android Jetpack Kotlin extensions Link: https://developer.android.com/kotlin/ktx • Named

    parameters • Parameter default values • Coroutines • Extension functions • Extension properties • Lambdas
  3. Core KTX: View implementation 'androidx.core:core-ktx:1.1.0' // 1.2.0-rc01 loadingView.visibility = if

    (isLoading) View.VISIBLE else View.GONE // with ktx loadingView.isVisible = isLoading
  4. Core KTX: View inline var View.isVisible: Boolean get() = visibility

    == View.VISIBLE set(value) { visibility = if (value) View.VISIBLE else View.GONE } inline var View.isInvisible: Boolean inline var View.isGone: Boolean // LayoutParams // Margin // Padding // and more ...
  5. Core KTX: Text val htmlString = "<b>bold</b> string" textView.text =

    Html.fromHtml(htmlString) // with compat textView.text = HtmlCompat.fromHtml(htmlString, FROM_HTML_MODE_LEGACY) // with ktx textView.text = htmlString.parseAsHtml()
  6. Core KTX: Text inline fun String.parseAsHtml( flags: Int = FROM_HTML_MODE_LEGACY,

    imageGetter: ImageGetter? = null, tagHandler: TagHandler? = null ): Spanned = HtmlCompat.fromHtml(this, flags, imageGetter, tagHandler)
  7. Core KTX: Text val sb = StringBuilder() .append(1) .append("string") //

    kotlin function val sb = buildString { append(1) append("string") } 1string
  8. Core KTX: Text implementation 'androidx.core:core-ktx:1.1.0' // 1.2.0-rc01 val ssb =

    SpannableStringBuilder().apply { append("bold") setSpan(StyleSpan(Typeface.BOLD), 0, 4, SPAN_INCLUSIVE_EXCLUSIVE) append("string") } // with ktx val ssb = buildSpannedString { bold { append("bold") } append("string") } boldstring
  9. Core KTX: Text inline fun buildSpannedString( builderAction: SpannableStringBuilder.() -> Unit):

    SpannedString { val builder = SpannableStringBuilder() builder.builderAction() return SpannedString(builder) } ... inline fun SpannableStringBuilder.bold( builderAction: SpannableStringBuilder.() -> Unit ) = inSpans(StyleSpan(BOLD), builderAction = builderAction)
  10. Core KTX: Text buildSpannedString { click(onClick = { /* click!

    */ }) { color(Color.BLUE) { bold { append("clickable") } } } append("string") } clickablestring Custom
  11. Core KTX: Text inline fun SpannableStringBuilder.click( crossinline onClick: (View) ->

    Unit, builderAction: SpannableStringBuilder.() -> Unit ) = inSpans( object : ClickableSpan() { override fun onClick(widget: View) = onClick(widget) }, builderAction = builderAction ) Custom
  12. Core KTX: OS fun newInstance(id: Int, title: String): DetailFragment {

    return DetailFragment().apply { arguments = Bundle().apply { putInt("EXTRA_ID", id) putCharSequence("EXTRA_TITLE", title) } } } // with ktx arguments = bundleOf( "EXTRA_ID" to id, "EXTRA_TITLE" to title )
  13. Core KTX: OS fun bundleOf(vararg pairs: Pair<String, Any?>) = Bundle(pairs.size).apply

    { for ((key, value) in pairs) { when (value) { is Int -> putInt(key, value) is CharSequence -> putCharSequence(key, value) is Parcelable -> putParcelable(key, value) ... } } }
  14. Collection KTX // Java Collections val array = arrayOf(1, 2,

    3) val list = listOf(1, 2, 3) val set = setOf("one", "two", "three") val map = mapOf(1 to "one", 2 to "two") // Android Collections with ktx val arraySet: ArraySet<String> = arraySetOf("one", "two", "three") val arrayMap: ArrayMap<Int, String> = arrayMapOf(1 to "one", 2 to "two")
  15. Collection KTX inline fun <K, V> arrayMapOf(): ArrayMap<K, V> =

    ArrayMap() fun <K, V> arrayMapOf(vararg pairs: Pair<K, V>): ArrayMap<K, V> { val map = ArrayMap<K, V>(pairs.size) for (pair in pairs) { map[pair.first] = pair.second } return map } ...
  16. Activity, Fragment, Navigation KTX AAC ViewModel View (Activity / Fragment)

    implementation 'androidx.activity:activity-ktx:1.0.0' // 1.1.0-rc02 'androidx.fragment:fragment-ktx:1.1.0' // 1.2.0-rc02 'androidx.navigation:navigation-fragment-ktx:2.1.0' // 2.2.0-rc02 'androidx.navigation:navigation-ui-ktx:2.1.0' // 2.2.0-rc02
  17. Activity, Fragment, Navigation KTX private lateinit var viewModel: HomeViewModel override

    fun onCreateView(...): View? { viewModel = ViewModelProviders.of(this).get(HomeViewModel::class.java) } // with lazy private val viewModel: HomeViewModel by lazy { ViewModelProviders.of(this).get(HomeViewModel::class.java) } // with ktx private val viewModel: HomeViewModel by viewModels()
  18. Activity, Fragment, Navigation KTX // Activity private val viewModel: HomeViewModel

    by viewModels() // Fragment (scope: activity) private val viewModel: HomeViewModel by activityViewModels() // Fragment (scope: fragment) private val viewModel: HomeViewModel by viewModels() // Fragment (scope: navigation graph) private val viewModel: HomeViewModel by navGraphViewModels(R.id.home_graph)
  19. Firebase KTX Link: https:// rebase.google.com/support/release-notes/android // build.gradle implementation 'com.google.firebase:firebase-common-ktx:19.3.0' implementation

    'com.google.firebase:firebase-firestore-ktx:21.3.0' implementation 'com.google.firebase:firebase-functions-ktx:19.0.1' implementation 'com.google.firebase:firebase-storage-ktx:19.1.0' implementation 'com.google.firebase:firebase-inappmessaging-ktx:19.0.2' implementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.0.2' implementation 'com.google.firebase:firebase-database-ktx:19.2.0'
  20. Play Core KTX Link: https://developer.android.com/reference/com/google/android/play/core/release-notes // build.gradle implementation 'com.google.android.play:core-ktx:1.6.4' val

    AppUpdateInfo.isFlexibleUpdateAllowed: Boolean val AppUpdateInfo.isImmediateUpdateAllowed: Boolean suspend fun AppUpdateManager.requestAppUpdateInfo(): AppUpdateInfo fun AppUpdateManager.requestUpdateFlow(): Flow<AppUpdateResult> ...
  21. DataBinding UI Link: https://developer.android.com/topic/libraries/data-binding • Goodbye findViewById() • BindingAdapter •

    Two-way data binding // app/build.gradle android { dataBinding { enabled = true } }
  22. DataBinding: example val loadingView = findViewById(R.id.loading_view) loadingView.visibility = if (isLoading)

    View.VISIBLE else View.GONE <View android:id="@+id/loading_view" android:visibility="@{isLoading ? View.VISIBLE : View.GONE}" />
  23. DataBinding: example <layout> <data> <import type="android.view.View"/> <variable name="isLoading" type="boolean" />

    </data> <ViewGroup> <View android:id="@+id/loading_view" android:visibility="@{isLoading ? View.VISIBLE : View.GONE}" /> </ViewGroup> </layout>
  24. DataBinding: visibility // HomeViewModel.kt private val _isLoading = MutableLiveData<Boolean>() val

    isLoading: LiveData<Boolean> get() = _isLoading init { _isLoading.value = true // load data _isLoading.value = false } // HomeActivity.kt private val viewModel: HomeViewModel by viewModels() val binding: HomeActivityBinding = ... viewModel.isLoading.observe(this, Observer { binding.loadingView.isVisible = it })
  25. DataBinding: visibility // HomeViewModel.kt private val _isLoading = MutableLiveData<Boolean>() val

    isLoading: LiveData<Boolean> get() = _isLoading init { _isLoading.value = true // load data _isLoading.value = false } // HomeActivity.kt private val viewModel: HomeViewModel by viewModels() val binding: HomeActivityBinding = ... binding.viewModel = viewModel
  26. DataBinding: visibility <layout> <data> <variable name="viewModel" type="HomeViewModel" /> </data> <ViewGroup>

    <View android:id="@+id/loading_view" android:visibleIf="@{viewModel.isLoading}" /> </ViewGroup> </layout> @BindingAdapter("android:visibleIf") fun View.setVisibleIf(value: Boolean) { isVisible = value }
  27. DataBinding: visibility // View.decompiled.java public final class ViewKt { @BindingAdapter({"android:visibleIf"})

    public static final void setVisibleIf(@NotNull View view, boolean value) { view.setVisibility(value ? View.VISIBLE : View.GONE); } } // Generated by DataBinding library // HomeActivityBindingImpl.java @Override protected void executeBindings() { com.example.util.ViewKt.setVisibleIf(this.loadingView, isLoading); }
  28. DataBinding: visibility // View.kt @BindingAdapter("android:visibleIf") fun View.setVisibleIf(value: Boolean) { isVisible

    = value } @BindingAdapter("android:invisibleIf") fun View.setInvisibleIf(value: Boolean) { isInvisible = value } @BindingAdapter("android:goneIf") fun View.setGoneIf(value: Boolean) { isGone = value } Custom
  29. DataBinding: click // HomeViewModel.kt fun onFilterButtonClick() { ... } //

    HomeActivity.kt private val viewModel: HomeViewModel by viewModels() val binding: HomeActivityBinding = ... binding.filterButton.setOnClickListener { viewModel.onFilterButtonClick() }
  30. DataBinding: click // HomeViewModel.kt fun onFilterButtonClick() { ... } //

    HomeActivity.kt private val viewModel: HomeViewModel by viewModels() val binding: HomeActivityBinding = ... binding.viewModel = viewModel <layout> <data>...</data> <ViewGroup> <View android:id="@+id/filter_button" android:onClick="@{() -> viewModel.onFilterButtonClick()}" /> </ViewGroup> </layout>
  31. <layout> <data> <variable name="viewModel" type="HomeViewModel" /> <variable name="item" type="Item" />

    </data> <ViewGroup> <TextView android:text="@{item.name}" android:onClick="@{() -> viewModel.onItemClick(item.id)}" /> </ViewGroup> </layout> DataBinding: click class Item( val id: Long, val name: String )
  32. DataBinding: checked // TermsViewModel.kt private val _isChecked = MutableLiveData<Boolean>(false) val

    isChecked: LiveData<Boolean> get() = _isChecked fun onTermsChecked(value: Boolean) { _isChecked.value = value } // TermsActivity.kt private val viewModel: TermsViewModel by viewModels() val binding: TermsActivityBinding = ... viewModel.isChecked.observe(this, Observer { binding.termsCheckbox.checked = it }) binding.termsCheckbox.setOnCheckedChangeListener { _, isChecked -> viewModel.onTermsChecked(isChecked) }
  33. DataBinding: checked // TermsViewModel.kt private val _isChecked = MutableLiveData<Boolean>(false) val

    isChecked: LiveData<Boolean> get() = _isChecked fun onTermsChecked(value: Boolean) { _isChecked.value = value } // TermsActivity.kt private val viewModel: TermsViewModel by viewModels() val binding: TermsActivityBinding = ... binding.viewModel = viewModel
  34. DataBinding: checked <layout> <data> <variable name="viewModel" type="TermsViewModel" /> </data> <ViewGroup>

    <CheckBox android:id="@+id/terms_checkbox" android:checked="@={viewModel.isChecked}" /> </ViewGroup> </layout>
  35. UseCase: Load Image URL ImageView , URL . Glide Image

    Loading Library , Kotlin . ViewModel View URL ImageView
  36. DataBinding: Load Image URL val url = "https://..." val imageView:

    ImageView = ... GlideApp.with(context).load(url).into(imageView) // kotlin extensions imageView.loadUrlAsync(url) val imageUrlFromApi = null imageView.loadUrlAsync(imageUrlFromApi) imageView.loadUrlAsync(imageUrlFromApi, placeholder = R.drawable.placeholder) fun ImageView.loadUrlAsync(url: Url?) { GlideApp.with(context).load(url).into(this) }
  37. DataBinding: Load Image URL fun ImageView.loadUrlAsync(url: Url?, placeholder: Drawable? =

    null) { if (url == null) { GlideApp.with(context).load(placeholder).into(this) } else { GlideApp.with(context).load(url) .apply { if (placeholder != null) { placeholder(placeholder) } } .into(this) } } Custom @BindingAdapter("srcUrl", "placeholder", requireAll = false)
  38. DataBinding: Load Image URL <ImageView android:id="@+id/image_view" android:scaleType="centerCrop" android:placeholder="@{@drawable/placeholder}" android:srcUrl="@{item.imageUrl}" tools:src="@tools:sample/avatars"

    /> // HomeItemBindingImpl.java @Override protected void executeBindings() { ImageViewKt.loadAsync( this.imageView, itemImageUrl, getDrawableFromResource(imageView, R.drawable.placeholder)); } class Item( val id: Long, val imageUrl: String? )
  39. DataBinding: Debounce Click val view: View = ... view.setOnClickListener {

    view -> startActivity(...) } // click! // click! click! view.setOnDebounceClickListener { view -> startActivity(...) } private typealias OnClickListener = (View) -> Unit fun View.setOnDebounceClickListener( listener: OnClickListener?) { if (listener == null) { setOnClickListener(null) } else { setOnClickListener(OnDebounceClickListener { it.run(listener) }) } }
  40. DataBinding: Debounce Click class OnDebounceClickListener(private val listener: OnClickListener) : View.OnClickListener

    { override fun onClick(v: View?) { val now = System.currentTimeMillis() if (now < lastTime + INTERVAL) return lastTime = now v?.run(listener) } companion object { private const val INTERVAL: Long = 300 private var lastTime: Long = 0 } } Custom
  41. DataBinding: Debounce Click <layout> <ViewGroup> <View android:id="@+id/filter_button" android:onClick="@{() -> viewModel.onFilterButtonClick()}"

    /> </ViewGroup> </layout> <layout> <ViewGroup> <View android:id="@+id/filter_button" android:onDebounceClick="@{() -> viewModel.onFilterButtonClick()}" /> </ViewGroup> </layout>
  42. DataBinding: Debounce Click @BindingAdapter("onDebounceClick") fun setOnDebounceClickListener(view: View, listener: View.OnClickListener?) {

    if (listener == null) {...} else { view.setOnClickListener(OnDebounceClickListener { it.run(listener::onClick) }) } } Custom // extension function fun View.setOnDebounceClickListener(listener: OnClickListener?) { if (listener == null) {...} else { setOnClickListener(OnDebounceClickListener { it.run(listener) }) } }
  43. Summary Android KTX , View Layer . DataBinding , Layout

    XML View . Android KTX, DataBinding .