RecyclerView와 Realm으로 만드는 Grid Layout

Android — 예상 시간: 25분

이 튜토리얼에서 Thorben Primke는 RecyclerView의 서브클래스인 RealmRecyclerView를 사용해서 렘에 저장된 데이터로부터 Grid Layout을 만드는 법을 알려줍니다.

앱에 grid layout을 넣는 방법을 찾고 있나요? 렘에 저장된 데이터로 이런 인터페이스를 정말 빨리 만들 수 있습니다. 더 좋은 점은 데이터를 자동 애니메이션의 트리거로 만들 수도 있다는 겁니다.

최종 결과물 코드는 GitHub에서 볼 수 있습니다.

완성 화면은 아래와 같습니다.

RealmRecyclerView Demo

튜토리얼

주의: NY Times의 API에서 데이터를 가져오려면 API Key가 필요합니다. NY Times Developer site에서 API Key를 무료로 받을 수 있습니다.

“Empty Activity” 템플릿으로 새 안드로이드 스튜디오 프로젝트를 만드세요.

jitpack.io에서 gradle dependency로 RealmSearchView를 프로젝트에 추가할 수 있습니다.

프로젝트의 build.gradle 파일에 아래 내용을 추가하세요.

maven { url "https://jitpack.io" }

결과는 다음과 같습니다.

allprojects {
    repositories {
        jcenter()
        maven { url "https://jitpack.io" }
    }
}

다음으로 app module의 build.gradle 파일에 아래 내용을 추가하세요.

compile 'com.github.thorbenprimke:realm-recycler-view:0.9.4'

이 튜토리얼에서는 샘플 데이터 세트이미지 로딩 라이브러리를 사용할 겁니다. 앱에 추가하려면 아래 내용을 app module의 build.gradle 파일에 추가하세요.

compile 'com.github.thorbenprimke:realm-nytimes-data:0.9.2'
compile 'com.github.bumptech.glide:glide:3.6.1'

dependency를 다시 동기화하면 준비 완료입니다.

인테그레이션

프로젝트의 기본 뼈대를 완성했으니 RealmRecyclerView를 통합하고 RealmBasedRecyclerViewAdapter의 서브클래스인 RealmRecyclerView를 구현해서 보여줄 차례입니다.

MainActivity의 레이아웃 파일을 열고 아래 내용으로 교체하세요.

<FrameLayout
    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="match_parent">

    <co.moonmonkeylabs.realmrecyclerview.RealmRecyclerView
        android:id="@+id/realm_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:rrvIsRefreshable="false"
        app:rrvLayoutType="Grid"
        app:rrvGridLayoutSpanCount="2"
        />
</FrameLayout>

이런 개발 뉴스를 더 만나보세요

뷰에는 세 가지 커스텀 속성이 지정돼 있습니다.

  • rrvIsRefreshable - RecyclerView의 당겨서 새로 고침 기능을 끕니다.
  • rrvLayoutType - RealmRecyclerView는 여러 레이아웃 타입을 지원하는데 이 튜토리얼에서는 2개 칼럼을 가진 Grid를 사용할 겁니다.
  • rrvGridLayoutSpanCount - rrvLayoutType를 Grid로 설정한 경우 이 속성을 0보다 크게 설정해야 합니다.

샘플 데이터에서 NYTimesStory (출처) 클래스를 사용합니다. adapter에서 하나의 스토리를 표현하기 위한 형식으로 이 클래스를 사용할 예정입니다.

NYTimesStory는 뷰의 집합으로 표현되는데 결과는 다음과 같습니다.

_------------------_
       Image
       Date
       Title
      Abstract
_------------------_

RealmBasedRecyclerViewAdapter 내의 ViewHolder에서 사용될 뷰는 grid_item_view.xml으로 명명했습니다. 레이아웃 폴더에 아래 내용으로 레이아웃을 생성하세요.

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="wrap_content"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/image"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_marginTop="5dp"
        android:layout_gravity="center_horizontal"/>

    <TextView
        android:id="@+id/title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="5dp"
        android:layout_marginRight="5dp"
        android:singleLine="true"
        android:ellipsize="end"
        android:textStyle="bold"/>

    <TextView
        android:id="@+id/date"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="5dp"
        android:layout_marginRight="5dp"
        android:layout_gravity="center_horizontal"
        android:singleLine="true"
        android:ellipsize="end"/>

    <TextView
        android:id="@+id/story_abstract"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="5dp"
        android:layout_marginRight="5dp"
        android:layout_marginBottom="5dp"
        android:gravity="center_horizontal"
        android:maxLines="5"
        android:ellipsize="end"/>
</LinearLayout>

NYTimesStory를 화면에 렌더링 하기 위해 데이터 모델은 위 레이아웃을 매핑합니다.

이제 RealmBasedRecyclerViewAdapter 클래스를 상속받아 서브 클래스를 만들어야 합니다.

public class NYTimesStoryRecyclerViewAdapter extends RealmBasedRecyclerViewAdapter<NYTimesStory,
        NYTimesStoryRecyclerViewAdapter.ViewHolder> {

    public NYTimesStoryRecyclerViewAdapter(
            Context context,
            RealmResults<NYTimesStory> realmResults,
            boolean automaticUpdate,
            boolean animateIdType) {
        super(context, realmResults, automaticUpdate, animateIdType);
    }

    public class ViewHolder extends RealmViewHolder {

        public TextView title;
        public TextView publishedDate;
        public ImageView image;
        public TextView storyAbstract;

        public ViewHolder(LinearLayout container) {
            super(container);
            this.title = (TextView) container.findViewById(R.id.title);
            this.publishedDate = (TextView) container.findViewById(R.id.date);
            this.image = (ImageView) container.findViewById(R.id.image);
            this.storyAbstract = (TextView) container.findViewById(R.id.story_abstract);
        }
    }

    @Override
    public ViewHolder onCreateRealmViewHolder(ViewGroup viewGroup, int viewType) {
        View v = inflater.inflate(R.layout.grid_item_view, viewGroup, false);
        ViewHolder vh = new ViewHolder((LinearLayout) v);
        return vh;
    }

    @Override
    public void onBindRealmViewHolder(ViewHolder viewHolder, int position) {
        final NYTimesStory nyTimesStory = realmResults.get(position);
        viewHolder.title.setText(nyTimesStory.getTitle());
        viewHolder.publishedDate.setText(nyTimesStory.getPublishedDate());
        final RealmList<NYTimesMultimedium> multimedia = nyTimesStory.getMultimedia();
        if (multimedia != null && !multimedia.isEmpty()) {
            Glide.with(GridExampleActivity.this).load(
                    multimedia.get(0).getUrl()).into(viewHolder.image);
        } else {
            viewHolder.image.setImageResource(R.drawable.nytimes_logo);
        }
        viewHolder.storyAbstract.setText(nyTimesStory.getStoryAbstract());
    }
}

onCreateRealmViewHolder 메서드는 grid_item_view를 inflate 해서 ViewHolder에 넘겨줍니다.

onBindRealmViewHolder 메서드에서 NYTimesStory가 view에 바인딩 됩니다.

원격지의 스토리 이미지를 불러오기 위해서 이미지 로딩 라이브러리인 Glide를 사용합니다.

요소 연결

필요 요소들을 다 만들었으니 이제 RealmRecyclerViewNYTimesStoryRecyclerViewAdapter를 연결해야겠죠.

액티비티에 아래 코드를 구현하세요.

private RealmRecyclerView realmRecyclerView;
private NYTimesStoryRecyclerViewAdapter nyTimesStoryAdapter;
private Realm realm;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_main_grid_layout);
    realmRecyclerView = (RealmRecyclerView) findViewById(R.id.realm_recycler_view);

    setTitle(getResources().getString(
            R.string.activity_layout_name,
            getIntent().getStringExtra("Type")));

    resetRealm();

    Realm.setDefaultConfiguration(getRealmConfig());
    realm = Realm.getDefaultInstance();
    RealmResults<NYTimesStory> nyTimesStories =
            realm.where(NYTimesStory.class).findAllSorted("sortTimeStamp", false);
    nyTimesStoryAdapter = new NYTimesStoryRecyclerViewAdapter(this, nyTimesStories, true, true);
    realmRecyclerView.setAdapter(nyTimesStoryAdapter);

    final NYTimesDataLoader nyTimesDataLoader = new NYTimesDataLoader();
    nyTimesDataLoader.loadAllData(realm, "YOUR_NY_TIMES_API_KEY");
}

@Override
protected void onDestroy() {
    super.onDestroy();
    realm.close();
    realm = null;
}

private RealmConfiguration getRealmConfig() {
    return new RealmConfiguration
            .Builder(this)
            .setModules(Realm.getDefaultModule(), new NYTimesModule())
            .build();
}

private void resetRealm() {
    Realm.deleteRealm(getRealmConfig());
}

액티비티의 onCreate 메서드 내에서는 렘 쿼리에 대한 응답으로 반환된 RealmResultsNYTimesStoryRecyclerViewAdapter로 전달됩니다. NYTimesStoryRecyclerViewAdapter의 생성자에는 2개의 boolean 매개 변수가 있습니다. 이를 통해 adapter가 자동적으로 새로운 Realm result를 보여주고 grid 아이템을 움직이도록 할 수 있습니다.

Realm의 스키마가 com.github.thorbenprimke:realm-nytimes-data라는 외부 라이브로리로부터 NYTimes의 모델을 포함하게 하려면 getRealmConfig() 메서드로 Realm의 기본 설정을 해야 합니다.

이 시점에서 애플리케이션을 실행할 수는 있지만 Realm이 비어 있으므로 아무것도 보이지 않을 겁니다.

기본적인 구조를 연결했으니 데이터를 준비해보죠. New York Times는 멋진 API를 제공하고 있는데 이 중에 “Top Stories” API를 사용해서 최신 뉴스 스토리를 불러와 grid에 보여줄 예정입니다.

초기 설정에서 realm-nytimes-data를 gradle dependency에 추가했었죠. 이 라이브러리는 NYTimesStory (출처)NYTimesMultimedium (출처) 두 가지의 렘 모델을 포함하고 있는데, 이를 통해 New York Times Top Stories API로부터 받은 데이터를 표현할 수 있습니다.

튜토리얼을 단순화하기 위해 API로부터 데이터를 요청하는 방법이나 받은 JSON 데이터를 디시리얼라이즈해서 Realm 객체*로 만드는 법을 다루진 않을 겁니다. 그 대신 API Key를 New York Times Developer site에 등록하기만 하면 됩니다.

그다음 아래처럼 액티비티 안의 YOUR_NY_TIMES_API_KEY를 본인의 API key로 바꾸세요.

nyTimesDataLoader.loadAllData(realm, "YOUR_NY_TIMES_API_KEY");

loadAllData 메서드는 API 응답을 불러오고 처리해서 렘에 스토리를 추가합니다.

데이터가 들어왔으니 애플리케이션을 실행해서 NYTimes의 뉴스 스토리 헤드라인을 아래처럼 grid에서 볼 수 있습니다.

RealmRecyclerView Demo

  • 데이터 요청과 JSON 파싱에 대해 궁금하다면 샘플 데이터 프로젝트NYTimesDataLoader (출처) 메서드 구현부를 보세요. 이 메서드는 제공된 섹션을 위한 async API 요청을 담당하고 JSON 응답을 파싱 해서 렘 모델로 삽입합니다.

다음: 안드로이드를 위한 Realm #6: Realm과 함께 하는 안드로이드의 단방향(Uni-directional) 아키텍처 안내서

General link arrow white

컨텐츠에 대하여

이 컨텐츠는 저자의 허가 하에 이곳에서 공유합니다.


Thorben Primke

Thorben is a Software Engineer at Pinterest working towards making all product pins buyable. Prior to Pinterest he worked on Android at Jelly, Facebook, and Gowalla.

4 design patterns for a RESTless mobile integration »

close