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

Transcript

66 slides
  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 .

More Decks by Sungyong An

Other Decks in Programming