본문 바로가기
Rx

안드로이드의 RxJava 활용 - 7(리액티브 리싸이클러뷰,설치된 앱 리스트 나열)

by 봄석 2018. 12. 30.

본 내용은 필자가 학습한 내용을 정리하는 내용입니다.

대부분 의 내용이 아래 책의 내용이므로 원서를 구매해서 직접보시는걸 추천드립니다!

RxJava 프로그래밍 리액티브 프로그래밍 기초부터 RxAndroid까지 한 번에

유동환 , 박정준 지음 | 한빛미디어 | 2017년 09월 04일 출간

http://www.kyobobook.co.kr/product/detailViewKor.laf?ejkGb=KOR&mallGb=KOR&barcode=9788968488658&orderClick=LAV&Kc=


저자님의 블로그

https://brunch.co.kr/@yudong#info




리액티브 리싸이클러뷰를 이용해 설치된 앱 리스트 나열하기

먼저 앱 레이아웃 구성하기.

앱레이아웃 구성 -1 프레그먼트부분

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
 
    <android.support.v7.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/recycler_view"
        android:scrollbars="vertical">
    </android.support.v7.widget.RecyclerView>
 

</RelativeLayout>



앱레이아웃 구성 -2 리싸이클러뷰 속안 개별 아이템 레이아웃

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">
 
    <ImageView
        android:id="@+id/item_image"
        android:layout_width="50dp"
        android:layout_height="50dp"/>
 
    <TextView
        android:id="@+id/item_title"
        android:paddingStart="5dp"
        android:paddingLeft="5dp"
        android:gravity="center_vertical"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</LinearLayout>

 



아이템레이아웃은 LinearLayout으로 정의하고 ImageView와 TextView로 구성합니다

설치된 애플리케이션의 정보를 읽어와서 아이콘과 이름을 각 ImageView,TextView에 바인딩합니다.


아래 코드는 구성한 화면에 출력할 RecyclerItem(데이터세트)클래스부분입니다.

import android.graphics.drawable.Drawable;
 
import lombok.AllArgsConstructor;
 
@AllArgsConstructor(staticName = "of")
public class RecyclerItem {
    Drawable image;
    String title;
}



리스트에 표시할 이미지와 타이틀로 구성된 클래스입니다. 코드가 생각보다 간단합니다. 이는 Lombok 라이브러리와 @Data 애너테이션으로 getter(), setter() , hashCode(), equals() 메소드 및 생성자를 자동으로 생성하게 만든 것입니다.


@AllArgsConstructor(staticName="of") 는  of() 메소드로 클래스에 선언된 모든 필드를 이용하여 객체를 생성할 수 있다는 의미입니다. 

즉, new 연산자가 아닌 static of() 메소드로 객체를 생성할 수 있습니다



이제 ViewHolder 클래스를 살펴보겠습니다.

    class MyViewHolder extends RecyclerView.ViewHolder {
        @BindView(R.id.item_image)
        ImageView mImage;
        @BindView(R.id.item_title)
        TextView mTitle;
 
        private MyViewHolder(View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
        }
 
        Observable<RecyclerItem> getClickObserver(RecyclerItem item) {
            return Observable.create(e -> itemView.setOnClickListener(view -> e.onNext(item)));
        }
    }



생성자의 인자로 itemView를 전달 받으면 super() 메소드를 이용하여 부모 클래스의 RecyclerView.ViewHolder에 정의되어 있는 public final View itemView에 값을 오버라이딩 합니다.다음에는 ButterKnife 라이브러리를 이용하여 itemView를 로컬 변수(mImage,mTitle)과 바인딩해줍니다.


안드로이드 프로그래밍에서는 생성자에 Click 리스너 이벤트를 넣어주는게 일반적이지만 리액티브 프로그래밍에서는 Click이벤트를 분리된 Observable에 생성합니다 .콜백지옥 부분을 이렇게 대체할 수 있습니다.



다음은 Adapter 클래스를 살펴볼차례입니다.

Adapter 클래스

class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.MyViewHolder> {
 
    private List<RecyclerItem> mItems = new ArrayList<>();
    private PublishSubject<RecyclerItem> mPublishSubject;
 
    RecyclerViewAdapter() {
        this.mPublishSubject = PublishSubject.create();
    }
 
    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        final View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_view_item, parent, false);
        return new MyViewHolder(view);
    }
 
    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        final RecyclerItem item = mItems.get(position);
        holder.mImage.setImageDrawable(item.getImage());
        holder.mTitle.setText(item.getTitle());
        holder.getClickObserver(item).subscribe(mPublishSubject);
    }
 
    @Override
    public int getItemCount() {
        return mItems.size();
    }
 
    public void updateItems(List<RecyclerItem> items) {
        mItems.addAll(items);
    }
 
    public void updateItems(RecyclerItem item) {
        mItems.add(item);
    }
 
    public PublishSubject<RecyclerItem> getItemPublishSubject() {
        return mPublishSubject;
    }
 
}



Adapter 클래스는 onCreateViewHolder(), onBindeViewHolder(), getItemCount() 라는 3개의 메소드를 구현해 주어야 합니다. 상세한 구현 내용은 다음과 같습니다.


1) onCreateViewHolder() 메소드를 이용해 직접 정의한 ViewHolder() 객체를 리턴합니다. 이 메소드는 viewType에 따라 최초 1회만 호출됩니다.

2) viewHolder() 객체가 생성되면 onBindeViewHolder() 메소드에서 holder 인자의 뷰 아이템에 값을 넣어 줍니다. 이후 mItems에 저장된 배열 요소 개수만큼 메소드가 호출됩니다.

3) Adapter클래스는 ListView 클래스의 ArrayAdapter 클래스처럼 List<Object>를 기본적으로 갖지 않습니다. 원하는 데이터 세트를 직접 구현해야 합니다. 아이템 정의가 끝나면 mItems에 저장된 배열 요소 개수를 리턴하는 getItemCount()를 구현해줍니다.

4) 뒤에서 설명할 RecycleViewFragment 클래스의 객체에서 데이터 세트를 전달받을 updateItems()를 구현합니다.

5) 어떤 아이템을 클릭했을 때 이벤트를 RecycleViewFragment 객체로 전달할 PublishSubject 객체도 선언합니다. 참고로 PublishSubject 객체라는 뜨거운 Observabled을 사용하는 이유는 구독자가없더라고 실시간 처리되어 소비해야하는 Click 이벤트의 특성 때문입니다


마지막으로 RecycleViewFramgnet 클래스의 리액티브 프로그래밍 부분을 살펴보겠습니다.

RecyceViewFragment.java-getItemObservable()

private Observable<RecyclerItem> getItemObservable() {
 
        final PackageManager pm = getActivity().getPackageManager();
        Intent i = new Intent(Intent.ACTION_MAIN, null);
        i.addCategory(Intent.CATEGORY_LAUNCHER);
 
        return Observable.fromIterable(pm.queryIntentActivities(i, 0))
                .sorted(new ResolveInfo.DisplayNameComparator(pm))
                .subscribeOn(Schedulers.io())
                .observeOn(Schedulers.io())
                .map(item -> {
                    Drawable image = item.activityInfo.loadIcon(pm);
                    String title = item.activityInfo.loadLabel(pm).toString();
                    return RecyclerItem.of(image, title);
                });
    }




PackageManager 클래스를 이용하여 설치된 앱 정보를 가져와 RecyclerItem 객체로 변경하는 간단한 함수입니다. queryIntentActivities() 메소드는 설치된 앱 중 CATEGORY_LANUNCHER 타입의 앱만 결과로 가져오게 됩니다. 가져온 결과는 앱 이름으로 정렬하고 이미지와 타이틀을 추출하여 RecyclerItem 객체를 생성합니다 .


RecycleViewFragment 클래스부분 

public class RecyclerViewFragment extends Fragment {
    public static final String TAG = RecyclerViewFragment.class.getSimpleName();
 
    @BindView(R.id.recycler_view)
    RecyclerView mRecyclerView;
 
    private RecyclerViewAdapter mRecyclerViewAdapter;
    private Unbinder mUnbinder;
 
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View layout = inflater.inflate(R.layout.fragment_recycler_view, container, false);
        ButterKnife.bind(this, layout);
        return layout;
    }
 
    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
 
        final LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
        mRecyclerView.setLayoutManager(layoutManager);
 
        mRecyclerViewAdapter = new RecyclerViewAdapter();
        mRecyclerView.setAdapter(mRecyclerViewAdapter);
        mRecyclerViewAdapter.getItemPublishSubject().subscribe(s -> toast(s.getTitle()));
    }
 
    @Override
    public void onStart() {
        super.onStart();
 
        if (mRecyclerViewAdapter == null) {
            return;
        }
 
        getItemObservable()
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(item -> {
                    mRecyclerViewAdapter.updateItems(item);
                    mRecyclerViewAdapter.notifyDataSetChanged();
                });
    }
 
 
    @Override
    public void onDestroyView() {
        super.onDestroyView();
        if (mUnbinder != null) {
            mUnbinder.unbind();
        }
        mUnbinder = null;
    }
 
 
    private Observable<RecyclerItem> getItemObservable() {
 
        final PackageManager pm = getActivity().getPackageManager();
        Intent i = new Intent(Intent.ACTION_MAIN, null);
        i.addCategory(Intent.CATEGORY_LAUNCHER);
 
        return Observable.fromIterable(pm.queryIntentActivities(i, 0))
                .sorted(new ResolveInfo.DisplayNameComparator(pm))
                .subscribeOn(Schedulers.io())
                .observeOn(Schedulers.io())
                .map(item -> {
                    Drawable image = item.activityInfo.loadIcon(pm);
                    String title = item.activityInfo.loadLabel(pm).toString();
                    return RecyclerItem.of(image, title);
                });
    }
 
 
    private void toast(String title) {
        Toast.makeText(getActivity().getApplicationContext(), title, Toast.LENGTH_SHORT).show();
    }
}





클래스에서 처리하는 부분은 두 가지 입니다, 리싸이클러뷰를 생성하는 부분과 리스트를 클릭하면 콜백을 받고 toast 팝업을 생성하는 부분입니다  다음과 같은 과정을 거칩니다


1) onActivityCreate() 메소드에서는 LayoutManager 객체와 Adapter 객체를 생성하여 리싸이클러 뷰에서 사용할 수 있도록 설정합니다.

2) onStart() 메소드가 호출되면 설치된 애플리케이션 정보가 RecyclerViewAdapter 객체에 업데이트됩니다.

3) 설치한 앱의 정보를 이용하여 아이콘과 이름으로 리스트 아이템을 구성하고 리스트를 클릭하면 앱의 이름을 toast 팝업으로 보여줍니다.



-------------------------------------------------------------------------------------------------


lombok 초기설정하기


참고한 블로그 - https://brunch.co.kr/@drcarter/3

- https://github.com/rzwitserloot/lombok/issues/1882

build중 발생한 오류 해결 -https://stackoverflow.com/questions/21674091/how-to-add-stacktrace-or-debug-option-when-building-android-studio-project



1)1. 우선 lombok plugin을 설치해야 합니다.

Preference -> Plugins -> Browse Repositories 에서 lombok을 검색하면 Lombok Plugin이 나옵니다. 이걸 설치해 줍니다.


    2. gradle에 lombok적용하기.

"""

compileOnly 'org.projectlombok:lombok:1.16.18'
annotationProcessor 'org.projectlombok:lombok:1.16.18'

"""


3. 트러블슈팅??

 - package javax.annotation does not exist

annotation does not exist...이걸 해결해 주기 위해서 gradle에 annotation을 compileOnly해줍니다.

"""

compileOnly 'org.glassfish:javax.annotation:10.0-b28'

"""


 - cannot find symbol class ConstructorProperties

lombok.config 설정파일을 추가해 줘야 합니다. 프로젝트와 같은 root에 lombok.config파일을 만들고

'''

lombok.anyConstructor.suppressConstructorProperties = true

와 같은 내용을 추가해 줍니다.



**lombok 1.18.4 버전이 최신버전인데 이 버전은 Android Gradle Plugin 버전 3.2.0에서 충돌하여 사용이 불가능한것 같다. 

해결책 Android Gradle Plugin과 Gradle 자체를 최신버전으로 업그레이드 하지만 lombok을 

1.16.18 버전으로 다운그레이드 하였다.

댓글