$30 off During Our Annual Pro Sale. View Details »

Kotlin Android Extensions and Anko

Kotlin Android Extensions and Anko

Presentation done at meetup group KotlinTLV

Alexander Gherschon

September 25, 2017
Tweet

More Decks by Alexander Gherschon

Other Decks in Programming

Transcript

  1. View Slide

  2. Together.
    Learn.

    View Slide

  3. Agenda
    • Kotlin Android Extensions
    • Anko
    • Coroutines

    View Slide

  4. Android Kotlin Extensions

    View Slide

  5. Kotlin Android Extensions (KAE)
    • Gradle Plugin
    • Replaces view injecting libraries
    • No more findViewsById()
    • Be careful

    View Slide

  6. KAE Use
    apply plugin: ‘kotlin-android-extensions'
    // Part of Kotlin IDEA Plugin so no extra dependencies

    View Slide

  7. KAE Use
    import kotlinx.android.synthetic.main.fragment_episodes.recyclerView
    android:id="@+id/recyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
    override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    recyclerView.setHasFixedSize(true)
    recyclerView.layoutManager = LinearLayoutManager(context)
    }

    View Slide

  8. KAE Magic?
    public View _$_findCachedViewById(int var1) {
    }
    private HashMap _$_findViewCache;
    if(this._$_findViewCache == null) {
    this._$_findViewCache = new HashMap();
    }
    View var2 = (View)this._$_findViewCache.get(Integer.valueOf(var1));
    if(var2 == null) {
    View var10000 = this.getView();
    if(var10000 == null) {
    return null;
    }
    var2 = var10000.findViewById(var1);
    this._$_findViewCache.put(Integer.valueOf(var1), var2);
    }
    return var2;

    View Slide

  9. KAE Decompilation
    public void onViewCreated(@Nullable View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    ((RecyclerView)this._$_findCachedViewById(id.recyclerView)).setHasFixedSize(true);
    ((RecyclerView)this._$_findCachedViewById(id.recyclerView))
    .setLayoutManager((LayoutManager)(new LinearLayoutManager(this.getContext())));
    }
    override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    recyclerView.setHasFixedSize(true)
    recyclerView.layoutManager = LinearLayoutManager(context)
    }
    // $FF: synthetic method
    public void onDestroyView() {
    super.onDestroyView();
    this._$_clearFindViewByIdCache();
    }

    View Slide

  10. KAE ViewHolder
    class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    fun bind(item: Item, listener: (Item) -> Unit) = with(itemView) {
    date.text = simpleDateFormatter.format(item.pubDate)
    name.text = item.title
    Picasso.with(context)
    .load(item.image!!.href)
    .placeholder(ContextCompat.getDrawable(context, R.drawable.placeholder))
    .into(image)
    description.text = item.description
    duration.text = item.duration?.value ?: ""
    card.setOnClickListener { listener(item) }
    }
    }

    View Slide

  11. KAE ViewHolder Decompilation
    public final void bind(@NotNull Item item, @NotNull Function1 listener) {
    Intrinsics.checkParameterIsNotNull(item, "item");
    Intrinsics.checkParameterIsNotNull(listener, "listener");
    View var3 = this.itemView;
    ((TextView)var3.findViewById(id.date)).setText(…);
    ((TextView)var3.findViewById(id.name)).setText((CharSequence)item.getTitle());
    (….)
    ((CardView)var3.findViewById(id.card)).setOnClickListener(…);
    }

    View Slide

  12. KAE ViewHolder The Right Way™
    class ViewHolder(override val containerView: View?) : RecyclerView.ViewHolder(containerView), LayoutContainer {
    fun bind(item: Item, listener: (Item) -> Unit) = with(itemView) {
    date.text = simpleDateFormatter.format(item.pubDate)
    name.text = item.title
    Picasso.with(context)
    .load(item.image!!.href)
    .placeholder(ContextCompat.getDrawable(context, R.drawable.placeholder))
    .into(image)
    description.text = item.description
    duration.text = item.duration?.value ?: ""
    card.setOnClickListener { listener(item) }
    }
    }
    public interface LayoutContainer {
    public val containerView: View?
    }

    View Slide

  13. KAE ViewHolder The Right Way™
    public View _$_findCachedViewById(int var1) {
    if(this._$_findViewCache == null) {
    this._$_findViewCache = new HashMap();
    }
    View var2 = (View)this._$_findViewCache.get(Integer.valueOf(var1));
    if(var2 == null) {
    View var10000 = this.getContainerView();
    if(var10000 == null) {
    return null;
    }
    var2 = var10000.findViewById(var1);
    this._$_findViewCache.put(Integer.valueOf(var1), var2);
    }
    return var2;
    }
    private HashMap _$_findViewCache;

    View Slide

  14. KAE ViewHolder The Right Way™
    public final void bind(@NotNull Item item, @NotNull Function1 listener) {
    Intrinsics.checkParameterIsNotNull(item, "item");
    Intrinsics.checkParameterIsNotNull(listener, "listener");
    ((TextView)this._$_findCachedViewById(id.date)).setText(…);
    ((TextView)this._$_findCachedViewById(id.name)).setText((CharSequence)item.getTitle());
    (..)
    ((CardView)this._$_findCachedViewById(id.card)).setOnClickListener(…);
    }
    // Same for Custom Views!

    View Slide

  15. Anko

    View Slide

  16. Anko
    • Anko Commons
    • Anko Layouts
    • Anko SQLite
    • Anko Coroutines

    View Slide

  17. Anko Commons - Intents
    fun startActivity(context: Context) {
    val intent = Intent(context, MainActivity::class.java)
    intent.putExtra(EXTRA_STATE, "3")
    context.startActivity(intent)
    }
    fun startActivity(context: Context) {
    context.startActivity(EXTRA_STATE to 3)
    }
    fun startActivity(context: Context) = context.startActivity(EXTRA_STATE to 3)
    context.share("url to a new podcast!”)

    View Slide

  18. Anko Commons - Dialogs
    context.alert("Hi, I'm Roy", "Have you tried turning it off and on again?") {
    yesButton { context.toast("Oh…") }
    noButton {}
    }.show()

    View Slide

  19. Anko Commons - Logging
    class EpisodesPresenter(private val callback: WeakReference): AnkoLogger {
    fun someFunction(item: Item) {
    info("clicked on $item")
    warn("This is a warning")
    }
    }

    View Slide

  20. Anko Commons - Misc
    // colors
    0xff0000.opaque // non-transparent red
    0x99.gray.opaque // non-transparent #999999 gray
    // Dimensions
    val dip = dip(20) // 60
    val sp = sp(20) // 60
    val px2dip = px2dip(100) // 33.333332

    View Slide

  21. Anko Layouts - before
    override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    return inflater!!.inflate(R.layout.fragment_episodes, container, false)
    }
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/viewSwitcher"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:inAnimation="@android:anim/fade_in"
    android:outAnimation="@android:anim/fade_out"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context=".fragment.EpisodesFragment">
    android:id="@+id/recyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center" />

    View Slide

  22. Anko Layouts - after
    override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    return UI {
    verticalLayout {
    padding = dip(30)
    editText {
    hint = "Name"
    textSize = 24f
    }
    editText {
    hint = "Password"
    textSize = 24f
    }
    button("Login") {
    textSize = 26f
    }
    }
    }.view
    }

    View Slide

  23. Anko SQLite
    class DatabaseOpenHelper(ctx: Context) : ManagedSQLiteOpenHelper(ctx, "MyDatabase", null, 1) {
    companion object {
    private var instance: DatabaseOpenHelper? = null
    @Synchronized
    fun getInstance(ctx: Context): DatabaseOpenHelper {
    if (instance == null) {
    instance = DatabaseOpenHelper(ctx.applicationContext)
    }
    return instance!!
    }
    }
    override fun onCreate(db: SQLiteDatabase) {
    // Here you create tables
    db.createTable("Customer", false,
    "id" to INTEGER,
    "name" to TEXT,
    "photo" to BLOB)
    }
    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
    // Here you can upgrade tables, as usual
    db.dropTable("Customer", true)
    }
    }

    View Slide

  24. Anko Coroutines

    View Slide

  25. Anko Coroutines - before
    object GetRssFeedRequest {
    fun getFeed(callback: Callback) {
    val retrofit = RetrofitUtils.getRetrofit()
    val service = retrofit.create(RssFeedService::class.java)
    val feed = service.getFeed()
    feed.enqueue(callback)
    }
    }

    View Slide

  26. Anko Coroutines - before
    fun getFeed(): LiveData> {
    val data = MutableLiveData>()
    GetRssFeedRequest.getFeed(object : Callback { // retrofit
    override fun onResponse(call: Call?, response: Response?) {
    val rss = response?.body()
    if (rss != null) {
    data.value = Resource.success(rss)
    }
    }
    override fun onFailure(call: Call?, t: Throwable?) {
    Log.d(TAG, t?.localizedMessage)
    data.value = Resource.error()
    }
    })
    return data
    }

    View Slide

  27. Anko Coroutines - after
    object GetRssFeedRequest {
    fun getFeed(): Response? {
    val retrofit = RetrofitUtils.getRetrofit()
    val service = retrofit.create(RssFeedService::class.java)
    val feed = service.getFeed()
    return feed.execute()
    }
    }

    View Slide

  28. Anko Coroutines - after
    fun getFeed(): LiveData> {
    val data = MutableLiveData>()
    async(UI) {
    val deferred = bg { GetRssFeedRequest.getFeed() }
    val response = deferred.await()
    if (response != null) {
    if(response.isSuccessful) {
    data.value = Resource.success(response.body()!!)
    } else {
    data.value = Resource.error()
    }
    }
    }
    return data
    }

    View Slide

  29. Anko Coroutines - after & shorter
    fun getFeed(): LiveData> {
    val data = MutableLiveData>()
    async(UI) {
    val response: Response? = bg { GetRssFeedRequest.getFeed() }.await()
    data.value = if(response?.isSuccessful == true) Resource.success(response.body()!!) else Resource.error()
    }
    return data
    }

    View Slide

  30. Anko Coroutines - Activity
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    async(UI) {
    val deferred: Deferred?> = bg {
    GetRssFeedRequest.getFeed()
    }
    val response: Response? = deferred.await()
    if(response != null && response.isSuccessful) {
    manageRss(response.body())
    }
    }
    } Any Leak here?

    View Slide

  31. Anko Coroutines - Activity TRW
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    val ref = asReference()
    async(UI) {
    val deferred: Deferred?> = bg {
    GetRssFeedRequest.getFeed()
    }
    val response: Response? = deferred.await()
    if(response != null && response.isSuccessful) {
    ref().manageRss(response.body())
    }
    }
    }
    No leaks!

    View Slide

  32. Questions?

    View Slide

  33. Giveaways!

    View Slide

  34. 3 Jetbrains licenses

    View Slide

  35. Kotlin In Action
    val coupon45 = “KOTLINTLV”

    View Slide

  36. Thank you
    @galex

    View Slide