Invincible의 RxKotlin

RxKotlin은 RxJava, RxAndroid에 비해서 참고 자료가 부족하네요...

 

요즘 한창 RxJava를 공부하고 있는데, 공부하면서 정리도 할겸 공유도 할겸 블로그 글을 써볼까 합니다.

 

Rx(Reactive) 시리즈는 java 뿐만 아니라 다른 언어에서도 많이 사용합니다. 전 안드로이드에서 사용하기 위해서 RxKotlin을 채택하고 연습하기로 했습니다. 먼저 제가 공부를 하고 있는 책은 "리액티브 프로그래밍 기초부터 RxAndroid까지 한번에 RxJava프로그래밍" 저자는 유동환, 박정준이라고 되어 있네요.

Rx프로그래밍은 RxJava나 RxAndroid나 RxKotlin이나 비슷한것 같네요.

 

책에 있는 예제를 RxKotlin을 사용해서 프로그래밍 해봤습니다. 처음이라 그런지 한참 헤맸습니다.

RxKotlin을 사용하기 위해서는

1. build.gradle에 아래와 같이 추가 해야 합니다.

implementation("io.reactivex.rxjava2:rxkotlin:2.4.0")
implementation "io.reactivex.rxjava2:rxandroid:2.1.1"

 

RxJava를 기반으로 RxAndroid가 나오고 이걸 기반으로 RxKotlin이 나오기 때문에 버전은 약간씩 다르네요. 가장 최신기술은 RxJava에 있겠네요.

 

제가 하려고 하는 작업은 

안드로이드의 설치된 앱정보를 가져와 RecyclerView에 나오게 하려고 합니다.

 

먼저 xml 입니다.

1. recycler_test_activity.xml 입니다.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical" />
</RelativeLayout>

2. 아답터에서 사용할 xml 모습입니다.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <ImageView
        android:id="@+id/thumb_iv"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="10dp" />

    <TextView
        android:id="@+id/text_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="10dp"
        android:layout_toRightOf="@+id/thumb_iv" />
</RelativeLayout>

 

XML은 전부 완성된것 같으니 Activity클래스의 코드를 작성할 차례네요.

 

package com.example.rxtest

import android.content.Context
import android.content.Intent
import android.content.pm.ResolveInfo
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import java.util.*
import kotlin.collections.ArrayList

class RecyclerTestActivity : AppCompatActivity() {

    private lateinit var recyclerView: RecyclerView
    private lateinit var context: Context

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.recycler_test_activity)

        context = this

        recyclerView = findViewById(R.id.recycler_view)
        recyclerView.apply {
            layoutManager = LinearLayoutManager(context)
            adapter = TestMenuListAdapter()
            setHasFixedSize(true)
        }

        val disposable = getItemObservable()
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe {
                (recyclerView.adapter as TestMenuListAdapter).run {
                    setItems(it)
                }
            }
    }

//    private fun getItemObservable(): Observable<RecyclerItem> {
//        val pm = packageManager
//        val i = Intent(Intent.ACTION_MAIN).run {
//            addCategory(Intent.CATEGORY_LAUNCHER)
//        }
//
//        return pm.queryIntentActivities(i, 0).toObservable()
//            .sorted(ResolveInfo.DisplayNameComparator(pm))
//            .subscribeOn(Schedulers.io())
//            .observeOn(Schedulers.io())
////            .doOnNext {
////                Log.d("AAA", "title : ${it.activityInfo.loadLabel(pm)}")
////            }
//            .map { item ->
//                val image = item.activityInfo.loadIcon(pm)
//                val title = item.activityInfo.loadLabel(pm).toString()
//                return@map RecyclerItem(image, title)
//            }
//    }

    private fun getItemObservable(): Observable<MutableList<RecyclerItem>> {
        val pm = packageManager
        val i = Intent(Intent.ACTION_MAIN).run {
            addCategory(Intent.CATEGORY_LAUNCHER)
        }

        return Observable.fromArray(pm.queryIntentActivities(i, 0))
            .subscribeOn(Schedulers.io())
            .observeOn(Schedulers.io())
            .map { unSortedList ->
                val sortedList = ArrayList(unSortedList)
                Collections.sort(sortedList, ResolveInfo.DisplayNameComparator(pm))
                return@map sortedList
            }
            .flatMap { sortedList ->
                Observable.fromIterable(sortedList)
                    .map { item ->
                        val image = item.activityInfo.loadIcon(pm)
                        val title = item.activityInfo.loadLabel(pm).toString()
                        RecyclerItem(image, title)
                    }
                    .toList()
                    .toObservable()
            }
    }

    private inner class TestMenuListAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
        private val VIEW_TYPE_ITEM = 1

        private var mItems = ArrayList<RecyclerItem>()

        fun setItems(items: MutableList<RecyclerItem>) {
            mItems.addAll(items)
            notifyDataSetChanged()
        }

        fun setItems(items: RecyclerItem) {
            mItems.add(items)
            notifyDataSetChanged()
        }


        override fun getItemCount(): Int {
            return mItems?.size
        }

        override fun getItemViewType(position: Int): Int {
            return VIEW_TYPE_ITEM
        }

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
            return ItemViewHolder(LayoutInflater.from(context).inflate(R.layout.app_item, parent, false))
        }

        override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) {
            when (viewHolder.itemViewType) {
                VIEW_TYPE_ITEM -> {
                    val vh = viewHolder as ItemViewHolder
                    val info = mItems?.get(position)
                    vh.textTv.text = info.title
                }
            }
        }

        private inner class ItemViewHolder(view: View) : RecyclerView.ViewHolder(view) {
            val thumbIv = view.findViewById(R.id.thumb_iv) as ImageView
            val textTv = view.findViewById(R.id.text_tv) as TextView
        }
    }

    private data class RecyclerItem(val thumb: Drawable?, val title: String?)
}

 

먼저 

Observable<RecyclerItem> 를 리턴하는 getItemObservable() 함수를 보시면 안드로이드 앱에서 설치된 앱리스트를 가져와서 각 아이템을 RecyclerItem으로 변경해서 아답터에 데이터를 추가합니다.

이러면 각 아이템에 대해서 notifyDataSetChanged()를 해야하는데, 이러면 효율이 떨어집니다. 

그래서 각 아이템을 list에 담아서 notifyDataSetChanged()를 한번만 할 수 있게 Observable<MutableList<RecyclerItem>> 를 리턴하도록 변경하였습니다. 더 좋은 방법이 있을 것 같은데, 저도 아직은 Rx와 안친해서 잘 모르겠네요.

 

각 함수 (map, flatmap, toList 등)를 적절히 조합하는게 최대 난제 인것 같습니다. 그럼 익숙해지는 그날까지 즐프요.

by Invincible Cooler 2020. 1. 15. 15:41
| 1 |