以前無心中關注了無碼科技的公衆號,由此知道了他們推出的第一個產品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目前有兩個:TopicViewModel
和NewsViewModel
,NewsViewModel
用於爲科技動態和開發者資訊提供數據,以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的做用不只侷限本篇這種形式,具體能夠參考官方文檔。歡迎你們一塊兒交流使用心得!