基於Kotlin、ViewModel、LiveData和LifeCycle開發的Readhub客戶端

背景

以前無心中關注了無碼科技的公衆號,由此知道了他們推出的第一個產品Readhub,地址爲readhub.me/,主要提供互聯網最新發生的新鮮事,關注了一段時間感受內容質量還不錯,可以幫咱們篩選掉必定的垃圾信息。可是它目前只能在瀏覽器和微信公衆號裏面查看,又加上本身一直想體驗一下谷歌推出的架構組件,因此在簡單分析了一下Readhub Web端的接口以後開發了一個Android版本的客戶端。GitHub地址user-gold-cdn.xitu.io/2018/1/10/1…java

效果圖

具體實現

App架構比較簡單:一個主Activity+三個Fragment。目前Readhub的信息只有三個分類,分別爲熱門話題、科技動態和開發者資訊。其中科技動態和開發者資訊數據模型相同,只是調用的就接口不一樣,能夠在很大程度上進行復用。api

項目目錄劃分以下,瀏覽器

和Android官方文檔建議的架構基本是一致。緩存

目前Repository中只是單純的從網絡請求數據,沒有作本地緩存,代碼以下微信

class DataRepository private constructor(context: Context) {
    private val SERVER_ADDRESS = "https://api.readhub.me/"
    private val httpService: Api

    init {
        val builder = Retrofit.Builder()
        builder.baseUrl(SERVER_ADDRESS)
        builder.client(DefaultOkHttpClient.getOkHttpClient(context))
        builder.addConverterFactory(GsonConverterFactory.create(GsonBuilder().create()))
        builder.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        val retrofit = builder.build()
        httpService = retrofit.create(Api::class.java)
    }

    /** * 熱門話題 */

    fun getTopics(lastCursor: Long?, pageSize: Int): Observable<PageResult<Topic>> {
        return httpService.getTopics(lastCursor, pageSize)
    }

    /** * 科技動態 */

    fun getTechNews(lastCursor: Long?, pageSize: Int): Observable<PageResult<News>> {
        return httpService.getTechNews(lastCursor, pageSize)
    }

    /** * 開發者資訊 */

    fun getDevNews(lastCursor: Long?, pageSize: Int): Observable<PageResult<News>> {
        return httpService.getDevNews(lastCursor, pageSize)
    }

    companion object {
        private var instance: DataRepository? = null
        fun getInstance(context: Context): DataRepository {
            if (instance == null) {
                synchronized(DataRepository::class.java) {
                    if (instance == null) {
                        instance = DataRepository(context)
                    }
                }
            }
            return instance!!
        }
    }
}


複製代碼

ViewModel目前有兩個:TopicViewModelNewsViewModelNewsViewModel用於爲科技動態和開發者資訊提供數據,以NewsViewModel爲例,網絡

class NewsViewModel(private val newsType: NewsType, private val pageSize:Int) : ViewModel() {

    private val liveData: MutableLiveData<List<News>> = MutableLiveData()
    private var isFirstPage = true
    private var lastCursor: Long = 0L
    private val newsList = ArrayList<News>()
    fun getLiveData(): LiveData<List<News>> {
        lastCursor = System.currentTimeMillis()
        fetchData()
        return liveData
    }

    fun refresh() {
        isFirstPage = true
        lastCursor = System.currentTimeMillis()
        fetchData()
    }

    fun loadMore() {
        isFirstPage = false
        fetchData()
    }

    private fun fetchData() {
        val observable = if (newsType == NewsType.TechNews) {
            DataRepository.getInstance(MyApplication.instance).getTechNews(lastCursor, pageSize)
        } else {
            DataRepository.getInstance(MyApplication.instance).getDevNews(lastCursor, pageSize)
        }
        observable.compose(SchedulerTransformer())
                .subscribe({ data ->
                    if (isFirstPage) {
                        newsList.clear()
                    }
                    newsList.addAll(newsList.size, data.data?.toList()!!)
                    liveData.value = newsList
                    lastCursor = data.data?.last()?.publishDate!!.toDate("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")?.time!!
                }, {
                    liveData.value = null
                })
    }
}

複製代碼

NewsViewModel有兩個構造參數newsType爲一個枚舉類型,用於區分是科技動態仍是開發者資訊,另外一個參數pageSize用於設置分頁大小。因爲NewsViewModel含有構造參數,因此咱們須要自定義它的建立方式,方式爲實現ViewProvider.Factory接口架構

class NewsViewModelFactory(private val newsType: NewsType, private val pageSize: Int) : ViewModelProvider.Factory {

    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(NewsViewModel::class.java)) {
            return NewsViewModel(newsType, pageSize) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

複製代碼

NewsViewModel自己封裝了下拉刷新和上拉加載的邏輯,而且提供了相應的方法。在Fragment中只須要在回調裏面觸發方法便可。這裏面的liveData使用是MutableLiveData便可變的LiveData,由於每次請求數據以後咱們須要從新設置liveData裏面的值。這樣的話在對應的Fragment中只須要監聽LiveData作好界面顯示邏輯就能夠了。ide

NewsFragment的代碼以下fetch

class NewsFragment : Fragment() {
    private val PAGE_SIZE = 10
    private var dataList: List<News> = ArrayList()
    private lateinit var newsViewModel: NewsViewModel
    private lateinit var newsLiveData: LiveData<List<News>>
    private var adapter: NewsListAdapter? = null
    private var newsType: NewsType = NewsType.TechNews

    private fun getObserver() = Observer<List<News>> { newsList ->
        if (newsList != null) {
            dataList = newsList
            if (adapter == null) {
                adapter = NewsListAdapter(context, dataList)
                adapter!!.onItemClickListener = onItemClickListener
                recyclerView.layoutManager = LinearLayoutManager(context)
                recyclerView.adapter = adapter
            } else {
                adapter?.data = dataList
            }
            smartRefreshLayout.finishLoadmore()
            smartRefreshLayout.finishRefresh()
            adapter!!.notifyDataSetChanged()
            recyclerView.scrollToPosition(dataList.size - PAGE_SIZE)
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        newsType = arguments?.getNewsType(KEY_NEWS_TYPE)!!
    }

    private val onItemClickListener = object : NewsListAdapter.OnItemClickListener {
        override fun onItemClick(view: View, position: Int) {
            val item = dataList[position]
            val intent = WebViewActivity.makeIntent(context, item.url, item.title, "")
            startActivity(intent)
        }
    }

    override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View {
        val view = inflater?.inflate(R.layout.news_fragment, container, false)
        return view!!
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        newsViewModel = ViewModelProviders.of(this, NewsViewModelFactory(newsType, PAGE_SIZE)).get(NewsViewModel::class.java)
        newsLiveData = newsViewModel.getLiveData()
        newsLiveData.observe(this, getObserver())
        smartRefreshLayout.setOnRefreshListener {
            newsViewModel.refresh()
        }
        smartRefreshLayout.setOnLoadmoreListener {
            newsViewModel.loadMore()
        }
    }

    companion object {
        val KEY_NEWS_TYPE = "KEY_NEWS_TYPE"
        fun newInstance(newsType: NewsType): NewsFragment {
            val fragment = NewsFragment()
            val bundle = Bundle()
            bundle.putNewsType(KEY_NEWS_TYPE, newsType)
            fragment.arguments = bundle
            return fragment
        }
    }
}

複製代碼

在onActivityCreated回調中建立ViewModel而且獲取LiveData進行監聽,在Observer的回調中進行RecycleView的顯示邏輯處理。關於下拉刷新和上拉加載這裏使用了SmartRefreshLayout,只須要在回調中觸發ViewModel中對應的方法,數據獲取成功以後一樣執行Observer中代碼邏輯。其餘代碼邏輯比較明顯就不在介紹了。ui

完整代碼能夠查看user-gold-cdn.xitu.io/2018/1/10/1…

App目前發佈在酷安應用市場www.coolapk.com/apk/name.dm…,歡迎下載試用

總結

按照Android官方建議項目中RxJava和LiveData選擇一個便可。咱們這裏兩個都使用了,這裏你們能夠根據結合本身的狀況選擇。使用LiveData能夠不用關心生命週期的問題,可是LiveData自己提供操做符沒有RxJava功能強大;若是選擇RxJava能夠結合Rxlifecyle使用來彌補關於生命週期的問題。總體來看Android提供這一套架構組件對咱們的開發仍是很是有指導意義的,尤爲是關於ViewModel的做用不只侷限本篇這種形式,具體能夠參考官方文檔。歡迎你們一塊兒交流使用心得!

相關文章
相關標籤/搜索