Converted tab fragments to MvRx
diff --git a/LottieSample/build.gradle b/LottieSample/build.gradle
index 0a02220..a9c8c04 100644
--- a/LottieSample/build.gradle
+++ b/LottieSample/build.gradle
@@ -61,8 +61,9 @@
   implementation 'android.arch.lifecycle:extensions:1.1.0'
   kapt "android.arch.lifecycle:compiler:1.1.0"
   implementation "com.android.support:customtabs:$supportLibVersion"
-  implementation 'com.airbnb.android:epoxy:2.16.1'
-  kapt 'com.airbnb.android:epoxy-processor:2.16.1'
+  implementation 'com.airbnb.android:epoxy:2.16.4'
+  kapt 'com.airbnb.android:epoxy-processor:2.16.4'
+  implementation 'com.airbnb.android:mvrx:0.5.0'
   implementation 'com.android.support.constraint:constraint-layout:1.1.0-beta5'
   implementation 'androidx.core:core-ktx:0.2'
   implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
diff --git a/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/BaseEpoxyFragment.kt b/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/BaseEpoxyFragment.kt
new file mode 100644
index 0000000..cd982d2
--- /dev/null
+++ b/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/BaseEpoxyFragment.kt
@@ -0,0 +1,36 @@
+package com.airbnb.lottie.samples
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import com.airbnb.epoxy.AsyncEpoxyController
+import com.airbnb.epoxy.EpoxyController
+import com.airbnb.mvrx.BaseMvRxFragment
+import kotlinx.android.synthetic.main.fragment_base.*
+import kotlinx.android.synthetic.main.fragment_base.view.*
+
+
+private class BaseEpoxyController(
+        private val buildModelsCallback: EpoxyController.() -> Unit
+) : AsyncEpoxyController() {
+    override fun buildModels() {
+        buildModelsCallback()
+    }
+}
+
+
+abstract class BaseEpoxyFragment : BaseMvRxFragment() {
+
+    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
+            inflater.inflate(R.layout.fragment_base, container, false).apply {
+                recyclerView.setController(BaseEpoxyController { buildModels() })
+            }
+
+
+    override fun invalidate() {
+        recyclerView.requestModelBuild()
+    }
+
+    abstract fun EpoxyController.buildModels()
+}
\ No newline at end of file
diff --git a/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/LottiefilesFragment.kt b/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/LottiefilesFragment.kt
index cba0db4..6bf7709 100644
--- a/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/LottiefilesFragment.kt
+++ b/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/LottiefilesFragment.kt
@@ -1,82 +1,103 @@
 package com.airbnb.lottie.samples
 
-import android.arch.lifecycle.Observer
-import android.arch.lifecycle.ViewModelProviders
-import android.os.Bundle
-import android.support.v4.app.Fragment
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
+import android.support.v4.app.FragmentActivity
 import com.airbnb.epoxy.EpoxyController
-import com.airbnb.epoxy.EpoxyRecyclerView
+import com.airbnb.lottie.samples.model.AnimationData
+import com.airbnb.lottie.samples.model.AnimationResponse
 import com.airbnb.lottie.samples.model.CompositionArgs
-import com.airbnb.lottie.samples.views.LoadingViewModel_
-import com.airbnb.lottie.samples.views.LottiefilesTabBarModel_
-import com.airbnb.lottie.samples.views.MarqueeModel_
-import com.airbnb.lottie.samples.views.SearchInputItemViewModel_
-import kotlinx.android.synthetic.main.fragment_epoxy_recycler_view.*
+import com.airbnb.lottie.samples.views.loadingView
+import com.airbnb.lottie.samples.views.lottiefilesTabBar
+import com.airbnb.lottie.samples.views.marquee
+import com.airbnb.lottie.samples.views.searchInputItemView
+import com.airbnb.mvrx.*
 
-private val TAG = LottiefilesFragment::class.simpleName
-class LottiefilesFragment : Fragment(), EpoxyRecyclerView.ModelBuilderCallback {
 
-    private val lottiefilesService by lazy { (requireContext().applicationContext as LottieApplication).lottiefilesService }
-    private val viewModel by lazy { ViewModelProviders.of(this).get(LottiefilesViewModel::class.java) }
+data class LottiefilesState(
+        val mode: LottiefilesMode = LottiefilesMode.Recent,
+        val items: List<AnimationData> = emptyList(),
+        val request: Async<AnimationResponse> = Uninitialized,
+        val query: String = ""
+) : MvRxState
 
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-        viewModel.fetchMoreAnimations()
+class LottiefilesViewModel(
+        initialState: LottiefilesState,
+        private val service: LottiefilesService) : MvRxViewModel<LottiefilesState>(initialState
+) {
+    init {
+        selectSubscribe(LottiefilesState::mode) { fetchMoreItems() }
     }
 
-    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
-            inflater.inflate(R.layout.fragment_epoxy_recycler_view, container, false)
+    fun fetchMoreItems() = withState { state ->
+        if (state.request is Loading) return@withState
+        val page = (state.request()?.currentPage ?: -1) + 1
+        if (state.request()?.lastPage == page && page > 0) return@withState
 
-    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        recyclerView.buildModelsWith(this)
-        viewModel.loading.observe(this, Observer { recyclerView.requestModelBuild() })
-        viewModel.animationDataList.observe(this, Observer { recyclerView.requestModelBuild() })
+        when (state.mode) {
+            LottiefilesMode.Recent -> service.getRecent(page)
+            LottiefilesMode.Popular -> service.getPopular(page)
+            LottiefilesMode.Search -> service.search(state.query)
+        }.execute { copy(request = it) }
     }
 
-    override fun buildModels(controller: EpoxyController) {
-        MarqueeModel_()
-                .id("lottiefiles")
-                .title(R.string.lottiefiles)
-                .subtitle(R.string.lottiefiles_airbnb)
-                .addTo(controller)
+    fun setMode(mode: LottiefilesMode, query: String = "") = setState {
+        if (this.mode == mode && mode != LottiefilesMode.Search) return@setState this
+        if (this.mode == mode && mode == LottiefilesMode.Search && this.query == query) return@setState this
 
-        LottiefilesTabBarModel_()
-                .id("mode")
-                .mode(viewModel.mode())
-                .recentClickListener { _-> viewModel.setMode(LottiefilesMode.Recent) }
-                .popularClickListener { _ -> viewModel.setMode(LottiefilesMode.Popular) }
-                .searchClickListener { _ -> viewModel.setMode(LottiefilesMode.Search) }
-                .addTo(controller)
+        copy(mode = mode, request = Uninitialized, items = emptyList(), query = query)
+    }
 
-        SearchInputItemViewModel_()
-                .id("search")
-                .searchClickListener { viewModel.setMode(LottiefilesMode.Search, it) }
-                .addIf(viewModel.mode() == LottiefilesMode.Search, controller)
-
-        val lastAnimationData = viewModel.animationDataList.value?.lastOrNull()
-        viewModel.animationDataList.value?.forEach {
-            it ?: return@forEach
-            val args = CompositionArgs(animationData = it)
-            AnimationItemViewModel_()
-                    .id(it.id)
-                    .animationData(it)
-                    .clickListener { _ ->
-                        startActivity(PlayerActivity.intent(requireContext(), args))
-                    }
-                    .onBind({ _, _, _ ->
-                        if (it == lastAnimationData) {
-                            viewModel.fetchMoreAnimations()
-                        }
-                    })
-                    .addTo(controller)
+    companion object : MvRxViewModelFactory<LottiefilesState> {
+        @JvmStatic
+        override fun create(activity: FragmentActivity, state: LottiefilesState): LottiefilesViewModel {
+            val service = (activity.applicationContext as LottieApplication).lottiefilesService
+            return LottiefilesViewModel(state, service)
         }
 
-        LoadingViewModel_()
-                .id("loading")
-                .onBind { _, _, _ -> viewModel.fetchMoreAnimations() }
-                .addIf(viewModel.loading.value ?: false, controller)
+    }
+}
+
+class LottiefilesFragment : BaseEpoxyFragment() {
+    private val viewModel: LottiefilesViewModel by fragmentViewModel()
+
+    override fun EpoxyController.buildModels() = withState(viewModel) { state ->
+        marquee {
+            id("lottiefiles")
+            title(R.string.lottiefiles)
+            subtitle(R.string.lottiefiles_airbnb)
+        }
+
+        lottiefilesTabBar {
+            id("mode")
+            mode(state.mode)
+            recentClickListener { _ -> viewModel.setMode(LottiefilesMode.Recent) }
+            popularClickListener { _ -> viewModel.setMode(LottiefilesMode.Popular) }
+            searchClickListener { _ -> viewModel.setMode(LottiefilesMode.Search) }
+        }
+
+        if (state.mode == LottiefilesMode.Search) {
+            searchInputItemView {
+                id("search")
+                searchClickListener { viewModel.setMode(LottiefilesMode.Search, it) }
+            }
+        }
+
+        state.items.forEach {
+            val args = CompositionArgs(animationData = it)
+            animationItemView {
+                id(it.id)
+                animationData(it)
+                clickListener { _ ->
+                    startActivity(PlayerActivity.intent(requireContext(), args))
+                }
+                onBind { _, _, _ -> viewModel.fetchMoreItems() }
+            }
+        }
+
+        if (state.request is Loading) {
+            loadingView {
+                id("loading")
+                onBind { _, _, _ -> viewModel.fetchMoreItems() }
+            }
+        }
     }
 }
\ No newline at end of file
diff --git a/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/LottiefilesViewModel.kt b/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/LottiefilesViewModel.kt
deleted file mode 100644
index 4244a03..0000000
--- a/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/LottiefilesViewModel.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-package com.airbnb.lottie.samples
-
-import android.app.Application
-import android.arch.lifecycle.AndroidViewModel
-import android.arch.lifecycle.Lifecycle
-import android.arch.lifecycle.MutableLiveData
-import android.arch.lifecycle.OnLifecycleEvent
-import android.util.Log
-import com.airbnb.lottie.L
-import com.airbnb.lottie.samples.model.AnimationData
-import com.airbnb.lottie.samples.model.AnimationResponse
-import io.reactivex.Observable
-import io.reactivex.android.schedulers.AndroidSchedulers
-import io.reactivex.disposables.CompositeDisposable
-import io.reactivex.schedulers.Schedulers
-
-class LottiefilesViewModel(application: Application) : AndroidViewModel(application) {
-
-    val animationDataList = MutableLiveData<List<AnimationData?>>()
-    val loading = MutableLiveData<Boolean>()
-    val mode = MutableLiveData<LottiefilesMode>().apply { value = LottiefilesMode.Recent }
-    private var disposables = CompositeDisposable()
-    private val responses = ArrayList<AnimationResponse>()
-
-    private var searchQuery: String? = null
-
-    fun mode() = mode.value ?: throw IllegalStateException("Mode must be set")
-
-    fun setMode(mode: LottiefilesMode, searchQuery: String? = null) {
-        this.searchQuery = searchQuery
-        disposables.dispose()
-        disposables = CompositeDisposable()
-        this.mode.value = mode
-        animationDataList.value = null
-        loading.value = false
-        responses.clear()
-        fetchMoreAnimations()
-    }
-
-    fun fetchMoreAnimations() {
-        if (loading.value == true) return
-
-        val page = (responses.lastOrNull()?.currentPage ?: -1) + 1
-        if (!responses.isEmpty() && page > responses.last().lastPage) return
-
-        val service = getApplication<LottieApplication>().lottiefilesService
-        val observable = when (mode()) {
-            LottiefilesMode.Recent -> service.getRecent(page)
-            LottiefilesMode.Popular -> service.getPopular(page)
-            LottiefilesMode.Search ->
-                if (searchQuery == null) Observable.empty() else service.search(searchQuery ?: "")
-        }
-
-        disposables.add(observable
-                .subscribeOn(Schedulers.io())
-                .observeOn(AndroidSchedulers.mainThread())
-                .retry(3)
-                .doOnSubscribe { loading.value = true }
-                .subscribe({
-                    responses.add(it)
-                    animationDataList.value = flatten(animationDataList.value, it.data)
-                }, {
-                    Log.d(L.TAG, "e#\t", it);
-                }, {
-                    loading.value = false
-                }))
-    }
-
-    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
-    fun cleanupDisposables() = disposables.dispose()
-}
\ No newline at end of file
diff --git a/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/MvRxViewModel.kt b/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/MvRxViewModel.kt
new file mode 100644
index 0000000..41ad0ea
--- /dev/null
+++ b/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/MvRxViewModel.kt
@@ -0,0 +1,6 @@
+package com.airbnb.lottie.samples
+
+import com.airbnb.mvrx.BaseMvRxViewModel
+import com.airbnb.mvrx.MvRxState
+
+open class MvRxViewModel<S : MvRxState>(initialState: S) : BaseMvRxViewModel<S>(initialState, BuildConfig.DEBUG)
\ No newline at end of file
diff --git a/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/PreviewFragment.kt b/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/PreviewFragment.kt
index 1cf9b1c..3e80d76 100644
--- a/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/PreviewFragment.kt
+++ b/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/PreviewFragment.kt
@@ -3,76 +3,93 @@
 import android.Manifest
 import android.app.Activity
 import android.app.AlertDialog
+import android.content.ActivityNotFoundException
 import android.content.Intent
 import android.content.pm.PackageManager
-import android.os.Bundle
 import android.support.design.widget.Snackbar
-import android.support.v4.app.Fragment
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
 import android.widget.ArrayAdapter
 import android.widget.EditText
 import android.widget.Toast
+import com.airbnb.epoxy.EpoxyController
 import com.airbnb.lottie.samples.model.CompositionArgs
-import kotlinx.android.synthetic.main.fragment_preview.*
+import com.airbnb.lottie.samples.views.marquee
+import kotlinx.android.synthetic.main.fragment_player.*
 
 private const val RC_FILE = 1000
 private const val RC_CAMERA_PERMISSION = 1001
-class PreviewFragment : Fragment() {
+class PreviewFragment : BaseEpoxyFragment() {
 
-    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
-        inflater.inflate(R.layout.fragment_preview, container, false)
-
-    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        qr.setOnClickListener {
-            if (requireContext().hasPermission(Manifest.permission.CAMERA)) {
-                startActivity(QRScanActivity.intent(requireContext()))
-            } else {
-                requestPermissions(arrayOf(Manifest.permission.CAMERA), RC_CAMERA_PERMISSION)
-            }
+    override fun EpoxyController.buildModels() {
+        marquee {
+            id("marquee")
+            title(R.string.preview_title)
         }
 
-        file.setOnClickListener {
-            try {
-                val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
-                    type = "*/*"
-                    addCategory(Intent.CATEGORY_OPENABLE)
-                }
-                startActivityForResult(Intent.createChooser(intent, "Select a JSON file"), RC_FILE)
-            } catch (ex: android.content.ActivityNotFoundException) {
-                // Potentially direct the user to the Market with a Dialog
-                Toast.makeText(context, "Please install a File Manager.", Toast.LENGTH_SHORT).show()
-            }
-        }
-
-        url.setOnClickListener {
-            val urlOrJsonView = EditText(context)
-            AlertDialog.Builder(context)
-                    .setTitle(R.string.preview_url)
-                    .setView(urlOrJsonView)
-                    .setPositiveButton(R.string.preview_load) { _, _ ->
-                        val args = CompositionArgs(url = urlOrJsonView.text.toString())
-                        startActivity(PlayerActivity.intent(requireContext(), args))
-                    }
-                    .setNegativeButton(R.string.preview_cancel) { dialog, _ -> dialog.dismiss() }
-                    .show()
-        }
-
-        assets.setOnClickListener {
-            val adapter = ArrayAdapter<String>(requireContext(), android.R.layout.select_dialog_item).apply {
-                requireContext().assets.list("").forEach {
-                    if (it.endsWith(".json") || it.endsWith(".zip")) {
-                        add(it)
-                    }
+        previewItemView {
+            id("qr")
+            title(R.string.preview_qr)
+            icon(R.drawable.ic_qr_scan)
+            clickListener { _ ->
+                if (requireContext().hasPermission(Manifest.permission.CAMERA)) {
+                    startActivity(QRScanActivity.intent(requireContext()))
+                } else {
+                    requestPermissions(arrayOf(Manifest.permission.CAMERA), RC_CAMERA_PERMISSION)
                 }
             }
-            AlertDialog.Builder(context)
-                    .setAdapter(adapter) { _, which ->
-                        val args = CompositionArgs(asset = adapter.getItem(which))
-                        startActivity(PlayerActivity.intent(requireContext(), args))
+        }
+
+        previewItemView {
+            id("file")
+            title(R.string.preview_file)
+            icon(R.drawable.ic_file)
+            clickListener { _ ->
+                try {
+                    val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
+                        type = "*/*"
+                        addCategory(Intent.CATEGORY_OPENABLE)
                     }
-                    .show()
+                    startActivityForResult(Intent.createChooser(intent, "Select a JSON file"), RC_FILE)
+                } catch (ex: ActivityNotFoundException) {
+                    // Potentially direct the user to the Market with a Dialog
+                    Toast.makeText(context, "Please install a File Manager.", Toast.LENGTH_SHORT).show()
+                }
+            }
+        }
+
+        previewItemView {
+            id("url")
+            title(R.string.preview_url)
+            icon(R.drawable.ic_network)
+            clickListener { _ ->
+                val urlOrJsonView = EditText(context)
+                AlertDialog.Builder(context)
+                        .setTitle(R.string.preview_url)
+                        .setView(urlOrJsonView)
+                        .setPositiveButton(R.string.preview_load) { _, _ ->
+                            val args = CompositionArgs(url = urlOrJsonView.text.toString())
+                            startActivity(PlayerActivity.intent(requireContext(), args))
+                        }
+                        .setNegativeButton(R.string.preview_cancel) { dialog, _ -> dialog.dismiss() }
+                        .show()
+            }
+        }
+
+        previewItemView {
+            id("assets")
+            title(R.string.preview_assets)
+            icon(R.drawable.ic_storage)
+            clickListener { _ ->
+                val adapter = ArrayAdapter<String>(requireContext(), android.R.layout.select_dialog_item)
+                requireContext().assets.list("").asSequence()
+                        .filter { it.endsWith(".json") || it.endsWith(".zip") }
+                        .forEach { adapter.add(it) }
+                AlertDialog.Builder(context)
+                        .setAdapter(adapter) { _, which ->
+                            val args = CompositionArgs(asset = adapter.getItem(which))
+                            startActivity(PlayerActivity.intent(requireContext(), args))
+                        }
+                        .show()
+            }
         }
     }
 
@@ -89,7 +106,7 @@
                 if (grantResults.firstOrNull() == PackageManager.PERMISSION_GRANTED) {
                     startActivity(QRScanActivity.intent(requireContext()))
                 } else {
-                    Snackbar.make(container, R.string.qr_permission_denied, Snackbar.LENGTH_LONG).show()
+                    Snackbar.make(coordinatorLayout, R.string.qr_permission_denied, Snackbar.LENGTH_LONG).show()
                 }
             }
         }
diff --git a/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/PreviewItemView.kt b/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/PreviewItemView.kt
index ff06595..c6830d8 100644
--- a/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/PreviewItemView.kt
+++ b/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/PreviewItemView.kt
@@ -1,11 +1,17 @@
 package com.airbnb.lottie.samples
 
 import android.content.Context
+import android.support.annotation.DrawableRes
 import android.util.AttributeSet
-import android.util.TypedValue
+import android.view.View
 import android.widget.LinearLayout
+import com.airbnb.epoxy.CallbackProp
+import com.airbnb.epoxy.ModelProp
+import com.airbnb.epoxy.ModelView
+import com.airbnb.epoxy.TextProp
 import kotlinx.android.synthetic.main.list_item_preview.view.*
 
+@ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT)
 class PreviewItemView @JvmOverloads constructor(
         context: Context,
         attrs: AttributeSet? = null,
@@ -15,20 +21,20 @@
     init {
         orientation = VERTICAL
         inflate(R.layout.list_item_preview)
+    }
 
-        attrs?.let {
-            val ta = context.obtainStyledAttributes(it, R.styleable.PreviewItemView)
-            val titleText = resources.getText(ta.getResourceId(R.styleable.PreviewItemView_titleText, 0))
-            val iconRes = ta.getResourceId(R.styleable.PreviewItemView_icon, 0)
+    @TextProp
+    fun setTitle(title: CharSequence) {
+        titleView.text = title
+    }
 
-            titleView.text = titleText
-            iconView.setImageResource(iconRes)
+    @ModelProp
+    fun setIcon(@DrawableRes icon: Int) {
+        iconView.setImageResource(icon)
+    }
 
-            ta.recycle()
-        }
-
-        val outValue = TypedValue()
-        getContext().theme.resolveAttribute(android.R.attr.selectableItemBackground, outValue, true)
-        setBackgroundResource(outValue.resourceId)
+    @CallbackProp
+    fun setClickListener(clickListener: View.OnClickListener?) {
+        container.setOnClickListener(clickListener)
     }
 }
\ No newline at end of file
diff --git a/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/ShowcaseFragment.kt b/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/ShowcaseFragment.kt
index f3fa425..876fbc3 100644
--- a/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/ShowcaseFragment.kt
+++ b/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/ShowcaseFragment.kt
@@ -1,24 +1,36 @@
 package com.airbnb.lottie.samples
 
-import android.arch.lifecycle.Observer
-import android.arch.lifecycle.ViewModelProviders
 import android.content.Intent
-import android.os.Bundle
-import android.support.v4.app.Fragment
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
+import android.support.v4.app.FragmentActivity
 import com.airbnb.epoxy.EpoxyController
-import com.airbnb.epoxy.EpoxyRecyclerView
+import com.airbnb.lottie.samples.model.AnimationResponse
 import com.airbnb.lottie.samples.model.CompositionArgs
 import com.airbnb.lottie.samples.model.ShowcaseItem
-import com.airbnb.lottie.samples.views.LoadingViewModel_
-import com.airbnb.lottie.samples.views.MarqueeModel_
-import com.airbnb.lottie.samples.views.ShowcaseAnimationItemViewModel_
-import com.airbnb.lottie.samples.views.ShowcaseCarouselModel_
-import kotlinx.android.synthetic.main.fragment_epoxy_recycler_view.*
+import com.airbnb.lottie.samples.views.loadingView
+import com.airbnb.lottie.samples.views.marquee
+import com.airbnb.lottie.samples.views.showcaseAnimationItemView
+import com.airbnb.lottie.samples.views.showcaseCarousel
+import com.airbnb.mvrx.*
 
-class ShowcaseFragment : Fragment(), EpoxyRecyclerView.ModelBuilderCallback {
+data class ShowcaseState(val response: Async<AnimationResponse> = Uninitialized) : MvRxState
+
+class ShowcaseViewModel(initialState: ShowcaseState, service: LottiefilesService) : MvRxViewModel<ShowcaseState>(initialState) {
+    init {
+        service.getCollection("lottie-showcase")
+                .retry(3)
+                .execute { copy(response = it) }
+    }
+
+    companion object : MvRxViewModelFactory<ShowcaseState> {
+        @JvmStatic
+        override fun create(activity: FragmentActivity, state: ShowcaseState): ShowcaseViewModel {
+            val service = (activity.applicationContext as LottieApplication).lottiefilesService
+            return ShowcaseViewModel(state, service)
+        }
+    }
+}
+
+class ShowcaseFragment : BaseEpoxyFragment() {
 
     private val showcaseItems = listOf(
             ShowcaseItem(R.drawable.showcase_preview_lottie, R.string.showcase_item_app_intro) {
@@ -40,48 +52,33 @@
                 startActivity(Intent(requireContext(), ListActivity::class.java))
             }
     )
-    private val viewModel by lazy { ViewModelProviders.of(this).get(ShowcaseViewModel::class.java) }
+    private val viewModel: ShowcaseViewModel by fragmentViewModel()
 
-    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
-            inflater.inflate(R.layout.fragment_epoxy_recycler_view, container, false)
-
-    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        recyclerView.buildModelsWith(this)
-
-        viewModel.collection.observe(this, Observer {
-            recyclerView.requestModelBuild()
-        })
-
-        viewModel.loading.observe(this, Observer {
-            recyclerView.requestModelBuild()
-        })
-
-        viewModel.fetchAnimations()
-    }
-
-    override fun buildModels(controller: EpoxyController) {
-        MarqueeModel_()
-                .id("showcase")
-                .title("Showcase")
-                .addTo(controller)
-        ShowcaseCarouselModel_()
-                .id("carousel")
-                .showcaseItems(showcaseItems)
-                .addTo(controller)
-
-        viewModel.collection.value?.data?.forEach {
-            ShowcaseAnimationItemViewModel_()
-                    .id(it.id)
-                    .title(it.title)
-                    .previewUrl(it.preview)
-                    .onClickListener { _ ->
-                        startActivity(PlayerActivity.intent(requireContext(), CompositionArgs(animationData = it)))
-                    }
-                    .addTo(controller)
+    override fun EpoxyController.buildModels() = withState(viewModel) { state ->
+        marquee {
+            id("showcase")
+            title("Showcase")
+        }
+        showcaseCarousel {
+            id("carousel")
+            showcaseItems(showcaseItems)
         }
 
-        LoadingViewModel_()
-                .id("loading")
-                .addIf(viewModel.loading.value ?: false, controller)
+        val collectionItems = state.response()?.data
+
+        if (collectionItems == null) {
+            loadingView {
+                id("loading")
+            }
+        } else {
+            collectionItems.forEach {
+                showcaseAnimationItemView {
+                    id(it.id)
+                    title(it.title)
+                    previewUrl(it.preview)
+                    onClickListener { _ -> startActivity(PlayerActivity.intent(requireContext(), CompositionArgs(animationData = it))) }
+                }
+            }
+        }
     }
 }
\ No newline at end of file
diff --git a/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/ShowcaseViewModel.kt b/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/ShowcaseViewModel.kt
deleted file mode 100644
index cf74c72..0000000
--- a/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/ShowcaseViewModel.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.airbnb.lottie.samples
-
-import android.app.Application
-import android.arch.lifecycle.AndroidViewModel
-import android.arch.lifecycle.MutableLiveData
-import android.util.Log
-import com.airbnb.lottie.samples.model.AnimationResponse
-import io.reactivex.android.schedulers.AndroidSchedulers
-import io.reactivex.disposables.CompositeDisposable
-import io.reactivex.schedulers.Schedulers
-
-private const val COLLECTION = "lottie-showcase"
-private val TAG = ShowcaseViewModel::class.java.simpleName
-class ShowcaseViewModel(application: Application) : AndroidViewModel(application) {
-
-    private val lottiefilesService by lazy { (application as LottieApplication).lottiefilesService }
-
-    private var disposables = CompositeDisposable()
-
-    val collection = MutableLiveData<AnimationResponse>()
-    val loading = MutableLiveData<Boolean>().apply { value = false }
-
-    fun fetchAnimations() {
-        disposables.add(lottiefilesService.getCollection(COLLECTION)
-                .subscribeOn(Schedulers.io())
-                .observeOn(AndroidSchedulers.mainThread())
-                .retry(3)
-                .doOnSubscribe { loading.value = true }
-                .subscribe({
-                    collection.value = it
-                }, {
-                    Log.e(TAG, "Error loading collection", it)
-                }, {
-                    loading.value = false
-                }))
-    }
-}
\ No newline at end of file
diff --git a/LottieSample/src/main/res/layout/fragment_base.xml b/LottieSample/src/main/res/layout/fragment_base.xml
new file mode 100644
index 0000000..05ad3cb
--- /dev/null
+++ b/LottieSample/src/main/res/layout/fragment_base.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/coordinatorLayout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <com.airbnb.epoxy.EpoxyRecyclerView
+        android:id="@+id/recyclerView"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+</android.support.design.widget.CoordinatorLayout>
\ No newline at end of file
diff --git a/LottieSample/src/main/res/layout/fragment_preview.xml b/LottieSample/src/main/res/layout/fragment_preview.xml
deleted file mode 100644
index 5d5506a..0000000
--- a/LottieSample/src/main/res/layout/fragment_preview.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:id="@+id/container"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="vertical">
-        <com.airbnb.lottie.samples.views.Marquee
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            app:titleText="@string/preview_title"/>
-
-        <com.airbnb.lottie.samples.PreviewItemView
-            android:id="@+id/qr"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            app:titleText="@string/preview_qr"
-            app:icon="@drawable/ic_qr_scan"/>
-
-        <com.airbnb.lottie.samples.PreviewItemView
-            android:id="@+id/file"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            app:titleText="@string/preview_file"
-            app:icon="@drawable/ic_file"/>
-
-        <com.airbnb.lottie.samples.PreviewItemView
-            android:id="@+id/url"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            app:titleText="@string/preview_url"
-            app:icon="@drawable/ic_network"/>
-
-        <com.airbnb.lottie.samples.PreviewItemView
-            android:id="@+id/assets"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            app:titleText="@string/preview_assets"
-            app:icon="@drawable/ic_storage"/>
-    </LinearLayout>
-</android.support.design.widget.CoordinatorLayout>
\ No newline at end of file
diff --git a/LottieSample/src/main/res/layout/list_item_preview.xml b/LottieSample/src/main/res/layout/list_item_preview.xml
index 64e7c9d..e44c95a 100644
--- a/LottieSample/src/main/res/layout/list_item_preview.xml
+++ b/LottieSample/src/main/res/layout/list_item_preview.xml
@@ -4,14 +4,16 @@
     tools:parentTag="android.widget.LinearLayout">
 
     <LinearLayout
+        android:id="@+id/container"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_marginBottom="18dp"
-        android:layout_marginLeft="24dp"
-        android:layout_marginRight="24dp"
-        android:layout_marginTop="18dp"
+        android:paddingBottom="18dp"
+        android:paddingLeft="24dp"
+        android:paddingRight="24dp"
+        android:paddingTop="18dp"
         android:gravity="center_vertical"
-        android:orientation="horizontal">
+        android:orientation="horizontal"
+        android:background="?attr/selectableItemBackground">
 
         <ImageView
             android:id="@+id/iconView"
diff --git a/LottieSample/src/main/res/values/attrs.xml b/LottieSample/src/main/res/values/attrs.xml
index 5f0ca96..94acd1a 100644
--- a/LottieSample/src/main/res/values/attrs.xml
+++ b/LottieSample/src/main/res/values/attrs.xml
@@ -1,10 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
     <attr name="titleText" format="string" />
-    <declare-styleable name="PreviewItemView">
-        <attr name="icon" format="reference" />
-        <attr name="titleText" />
-    </declare-styleable>
     <declare-styleable name="ControlBarItemToggleView">
         <attr name="src" format="reference" />
         <attr name="text" format="string" />
diff --git a/build.gradle b/build.gradle
index 6ebcd4a..c43e143 100644
--- a/build.gradle
+++ b/build.gradle
@@ -3,6 +3,7 @@
 buildscript {
   ext.kotlinVersion = '1.2.51'
   ext.supportLibVersion = '27.1.1'
+  ext.navVersion = '1.0.0-alpha04'
 
   repositories {
     jcenter()