Epoxy - Retrofit 통신 버전

매일 수정하는 GNOSS LV5·2021년 10월 18일
1

AndroidStudio

목록 보기
26/83
post-thumbnail

Epoxy라이브러리 사용 2탄

Epoxy는 Recyclerview의 어댑터부분을 단순하게 바꾸어 준다는 면에서 가치가 크다고 생각하지만
코틀린을 사용하는 입장으로 관련 레퍼런스가 너무 적어서 구현하는데 어려움이 있었다.
많은 것을 설명 할 수 없지만 나의 경우를 포스팅 함으로써 누군가에게 도움이 되길 바란다.


Epoxy는 기본적으로 여러가지 Recyclerview를 한 화면에서 복합적으로 보여주는것을 편하게 하기 위해서 사용한다.

그리고 대부분의 상황에서 Recyclerview를 사용한다면 서버에서 데이터를 받아와서 보여줄 것이다.
이번 포스팅의 목적은 더미데이터가 아닌 서버의 데이터를 이용한 예시이다.

예시의 조건은

  1. 서버가 존재한다.
  2. Carousel을 이용하여 Viwepager효과를 준다.
  3. Factory와 Controller를 구분한다.
  4. Glide를 사용한다.
    별개로
    서버 호출을 위한 Retrofit객체는 Hilt를 통해 생성한다.

구성은 다음과 같다.

  1. Model의 Dataclass
  2. Models
  3. item의 xml
  4. Factory
  5. Controller
  6. 이 모든것을 호출할 Fragment

Model의 Dataclass

data class ItemViewpager(
    val id: String,
    val videoBackground: String = "",
    val thumbTitle: String = "",
    val videoColor: String = ""
)

Model에서 사용할 예정입니다.


Model

//1번
@EpoxyModelClass(layout = R.layout.item_home_viewpager)
abstract class HomeViewPagerModel :
//2번
    EpoxyModelWithHolder<HomeViewpagerItemsHolder>() {
//3번
    @EpoxyAttribute
    lateinit var viewpager: ItemViewpager
//5번
    override fun bind(holder: HomeViewpagerItemsHolder) {
        with(holder) {
            viewpagerColor.setBackgroundColor(Color.parseColor(viewpager.videoColor))
            this.thumbTitle.text = viewpager.thumbTitle
            holder.glide.load(viewpager.videoBackground).into(holder.videoBackgroundImage)
        }
    }
}

//4번
class HomeViewpagerItemsHolder(parent: ViewParent) : KotlinHolder() {
    val thumbTitle by bind<TextView>(R.id.thumbTitle)
    val videoBackgroundImage by bind<ImageView>(R.id.videoBackgroundImage)
    val viewpagerColor by bind<ImageView>(R.id.viewpagerColor)
    val glide = Glide.with((parent as View).context)
}

1번

EpoxyModelClass 어노테이션을 달고 아이템xml을 연결해준다.
(item_home_viewpager의 코드는 다음에 있습니다.)

2번
EpoxyModelWithHolder를 이용하여 Holder를 지정해줍니다.
(Recyclerview의 홀더를 지정하는것과 같은 역할이며 어떤 컴포넌트들이 있는지 지정합니다.)

3번

EpoxyAttribute 어노테이션으로 사용할 데이터 클래스를 연결해줍니다.

4번

class HomeViewpagerItemsHolder를 만들어 줍니다.
생성자 parent : ViewParent는 Glide를 사용하기 위해 받아줍니다.
(대부분의 서버에서 이미지를 받아오는 경우에는 url을 사용할 것이고 예시에서는 Glide를 사용할 예정입니다.
Glide에서는 Context가 필요합니다.)
xml의 텍스트뷰, 이미지뷰를 변수로 연결시켜줍니다.
val glide의 경우 Glide를 사용할 것이고 Context를 넣어둡니다.
예시의 경우 받아오는 context는 HomeMainFragment와 연결될 것입니다.

5번

override bind를 이용하여 Holder에서 정해준 변수에 이벤트를 걸어줍니다.
(예를들면, 텍스트뷰에 어떤 텍스트를 넣을것이고 어떤 이미지뷰에 슨 이미지를 넣을것인지 입니다.)
viewpager.~~~에 들어가는 데이터들은 추후에 서버와의 통신을 통해 받아올 예정입니다.
서버와의 통신은 Fragment에서 진행하고 받아온 Response를 DataClass에 매칭해주는 작업은 Factory에서 진행합니다.


Item의 xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="wrap_content"
    android:layout_height="400dp">

    <androidx.cardview.widget.CardView
        android:id="@+id/viewPagerCard"
        android:layout_width="248dp"
        android:layout_height="248dp"
        android:layout_marginTop="68dp"
        android:elevation="10dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <ImageView
            android:id="@+id/videoBackgroundImage"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="fitXY" />

        <ImageView
            android:id="@+id/viewpagerColor"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:alpha="0.4"/>

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:id="@+id/thumbTitle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="20dp"
                android:layout_marginTop="60dp"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

        </androidx.constraintlayout.widget.ConstraintLayout>
    </androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>

item입니다. Viewpager에 한개씩 들어갈 아이템의 layout입니다.


Factory

class HomeViewPagerFactory {
    private val colorFilters = arrayListOf(
        "#05C817",
        "#e8cf09",
        "#E34F00",
        "#c01212",
        "#ca08d4",
        "#8c29f8",
        "#0b80f1",
        "#e8e8e8",
        "#04d6da",
        "#1219ee"
    )

//1번
    fun settingViewpager(response: Response<TEST>): List<ItemViewpager> {
//2번
		    val viewPagerItems = mutableListOf<ItemViewpager>()
//3번
        if (response.isSuccessful) {
				    val random = Random()
            val content = response.body()!!.content
            var i = 0
//// 별개
            var a = random.nextInt(10)
            repeat(9) {
                if (a == 9) {
                    a = 0
                }
//4번
                viewPagerItems.add(
                    ItemViewpager(
                        content[i].id.toString(),
                        content[i].thumbnailUrl,
                        content[i].title,
                        colorFilters[a]
                    )
                )
                a++
                i++
            }
        } else {
//5번
            showErrorLog(response.errorBody())
        }
//6번
        return viewPagerItems
    }
}

1번

Fragment에서 호출할 예정입니다.
생성자로 Rest의 통신결과를 받아옵니다. 성공여부를 알기 위해서 Response형태로 받아옵니다.
결과값으로 List를 내놓습니다.

편하게 생각하면 다음과 같은 순서입니다.
Retrofit통신 결과로 Response로 받습니다.
이건 서버에서 정해준 값이므로 바꿀수 없습니다.

받은 Response를 우리가 쓰기 편하게 가공합니다.
가공의 결과로 List를 내놓습니다.
우리는 이 리스트를 가지고 Controller에 넘겨줄 예정입니다.

2번

받아온 데이터를 정리할 보따리 = mutablelist를 만들어줍니다.

3번

서버의 통신결과가 성공했을때 입니다. reponse 값을 content에 넣어줍니다.그리고 repeat을 통해 반복하여 우리가 만든 보따리에 원하는대로 정리해줍니다.

4번

보따리에 넣는 과정입니다.
ItemViewpager데이터 클래스의 형식으로 넣겠습니다.
다시 말씀드리자면 content = 서버에서 받아온 값 입니다.
리스트의 형태로 담겨져있는것중에 우리가 필요한것들을 추출해서 보따리에 넣습니다.

id, background , title, color의 순서이며 color는 Factory에서 뽑습니다.

5번

통신결과가 에러가 발생했을 경우 에러 로그를 보여줍니다.
보통은 서버에서 에러응답이 정해져있습니다.

6번

보따리를 반환합니다.


Controller

class HomeEpoxyController : EpoxyController() {

//1번
    var viewpagerItem by Delegates.observable(emptyList<ItemViewpager>()) { _, _, _ ->
        requestModelBuild()
    }
//2번
    override fun buildModels() {
//3번
        CarouselModel_()
            .id("viewpagerList")
            .numViewsToShowOnScreen(1.3f)
            .models(viewpagerItem.map { viewpager ->
                HomeViewPagerModel_()
                    .id(viewpager.id)
                    .viewpager(viewpager)
            })
            .addTo(this)
    }
}

거의다 끝나갑니다 ! 조금만 더 힘내세요!

1번

viewpagerItem이라는 변수입니다. ItemViewpager라는 데이터클래스의 형식을 가지고있는 비어있는 리스트입니다.
Fragment에서 서버와 통신 → Fragment에서 Factory를 호출하여 세팅을 하고 → 그 세팅한 값들이 여기에 매칭되는 것입니다.
Delegate를 통하여 Observable로 만들어줍니다.

이것을 하는 목적은 단순합니다.
저희는 지금 Factory에서 repeat을 이용하여 서버의값을 리스트에 넣어주고있습니다.
1번세트, 2번세트, 3번세트 ....
몇번 세트까지 담길지 모릅니다.
이걸 Observable로 만들면 데이터가 변화할때마다 callback을 받을 수 있습니다.

콜백으로 requestModel을 호출합니다. 즉 데이터가 변화하였습니다 = 리스트가 추가되었습니다.

2번

buildModel이 호출됩니다.

3번 CarouselModel()이란 회전목마 모델이라고 불리며 Viewpager입니다. Epoxy에 내장되어있습니다.
numViewsToShowOnScreen = 몇개의 아이템을 보여줄것인지


Fragment

@AndroidEntryPoint
class HomeMainFragment : Fragment() {
    lateinit var binding: FragmentHomeMainBinding
//1번
    @Inject
    lateinit var service: ApiService
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        binding = FragmentHomeMainBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        initEpoxyRecyclerView()
    }

//2번
    private fun initEpoxyRecyclerView() {
				val job = CoroutinScope(Dispatchers.IO + SupervisorJob)
		    val singleViewPagerController: HomeEpoxyController by lazy { HomeEpoxyController() }
        val epoxyrecyclerView = binding.homeEpoxyRecyclerView
        job.launch {
            val response = service.getVideo(11)
            singleViewPagerController.apply {
                viewpagerItem = HomeViewPagerFactory().settingViewpager(response)
            }
            epoxyrecyclerView.apply {
                setController(singleViewPagerController)
            }
        }
    }
}

1번

Hilt를 이용하여 Retrofit객체를 생성한 것입니다. 이번 예시에서는 다루지 않습니다.

2번

서버와의 통신을 위해 job객체를 만듭니다.
HomeEpoxyController를 불러줍니다.
response는 서버와의 통신을 한 결과입니다.
EpoxyContoller에 apply해줍니다.
Controller의 viewpagerItem기억나시나요? Delegate.Observable 입니다.
여기에 Factory에서 settingViewpager를 통해 Response → 내가원하는 리스트 로 변환해줍니다.
생성자에 response를 넣어줍니다.
HomeMainFragment의 homeEpoxyRecyclerview에 컨트롤러를 세팅해주는것으로 마무리합니다.

저의 작업물에 부분부분 수정을 통하여 작성한 포스팅입니다.

에폭시를 쓸수록 편한것을 느끼지만 아직 다양한 레퍼런스가 없는것 같습니다.
전부 Food관련된 똑같은 예제들이고 불편함이 느껴져서 작성하였습니다.

profile
러닝커브를 따라서 등반중입니다.

0개의 댓글