Xây dựng ứng dụng Viblo trên android bằng kotlin sử dụng lib jsoup (Phần 1)
Với chúng ta thì trang web viblo.asia đã quá quen thuộc rồi, nhưng việc xem nó trên di động không thích hợp cho lắm vì có nhiều thành phần không cần thiết - > Từ những điều đó mình đã lên ý tưởng viết 1 app Viblo bằng kotlin và sử dụng thư viện jsoup Sau đây mình sẽ viết 1 series các bài viết ...
Với chúng ta thì trang web viblo.asia đã quá quen thuộc rồi, nhưng việc xem nó trên di động không thích hợp cho lắm vì có nhiều thành phần không cần thiết - > Từ những điều đó mình đã lên ý tưởng viết 1 app Viblo bằng kotlin và sử dụng thư viện jsoup Sau đây mình sẽ viết 1 series các bài viết hướng dẫn thực hiện ý tưởng này
1.1. activity_main.xml!
-
Cấu trúc gồm 1 BottomNavigationView mình đặt lên trên cùng và 1 FrameLayout Trong đó BottomNavigationView sẽ có 3 tab là Post , Questions và Discussions
-
Full code:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_awidth="match_parent" android:layout_height="match_parent" android:background="@android:color/white" android:orientation="vertical" tools:context="com.asia.viblo.view.activity.home.MainActivity"> <android.support.design.widget.BottomNavigationView android:id="@+id/bottomNavigationHome" android:layout_awidth="match_parent" android:layout_height="@dimen/size_56" android:layout_gravity="start" app:elevation="@dimen/size_1" app:itemBackground="@drawable/bg_bottom_navigation" app:itemIconTint="@color/selector_color_bottom_navigation" app:itemTextColor="@color/selector_color_bottom_navigation" app:menu="@menu/menu_navigation_items"/> <FrameLayout android:id="@+id/frameHome" android:layout_awidth="match_parent" android:layout_height="0dp" android:layout_weight="1"/> </LinearLayout>
1.2. menu_navigation_items.xml
Tạo 1 file menu giao diện cho BottomNavigationView ở thư mục res -> menu -> menu_navigation_items.xml Gồm 3 tab Post , Questions và Discussion
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/itemPost" android:icon="@drawable/ic_post" android:title="Post"/> <item android:id="@+id/itemQuestions" android:icon="@drawable/ic_questions" android:title="Questions"/> <item android:id="@+id/itemDiscussion" android:icon="@drawable/ic_discussion" android:title="Discussions"/> </menu>
1.3. MainActivity
- Full code : MainActivity.kt
Khởi tạo listener cho BottomNavigationView
private fun initBottomNavigationView() { bottomNavigationHome.setOnNavigationItemSelectedListener { item -> val fragment: Fragment = when (item.itemId) { R.id.itemPost -> PostFragment() R.id.itemQuestions -> QuestionsFragment() R.id.itemDiscussion -> DiscussionsFragment() else -> PostFragment() } supportFragmentManager.beginTransaction().replace(R.id.frameHome, fragment).commit() true } bottomNavigationHome.selectedItemId = R.id.itemPost }
- Cấu trúc của 1 fragment extend BaseFragment gồm có :
- 1 Spinner ở trên cùng
- Phần content ở giữa
- Ở cuối cùng là 1 layout : include_layout_next_back_page.xml để chuyển trang
-
Full code : BaseFragment.kt
-
Tạo listener chọn trang
- textPageNext để đến trang tiếp theo
- textPageBack để trở lại trang trước đó
- textPagePresent khi ấn vào đây sẽ hiển thị dialog chọn trang theo ý muốn của mình
open fun initListener() { textPageNext.setOnClickListener { val pageNext = SharedPrefs.instance[keyPagePresent, String::class.java].toInt() + 1 loadData(getLink(mPosition), pageNext.toString()) } textPageBack.setOnClickListener { val pageBack = SharedPrefs.instance[keyPagePresent, String::class.java].toInt() - 1 loadData(getLink(mPosition), pageBack.toString()) } textPagePresent.setOnClickListener { val builder = AlertDialog.Builder(context) val dialogSelectPage = DialogSelectPage(context, null, this) as LinearLayout dialogSelectPage.txtTitle.text = getString(R.string.text_dialog_title) dialogSelectPage.txtMessage.text = String.format( getString(R.string.text_dialog_message, "1", SharedPrefs.instance[keyMaxPage, String::class.java])) dialogSelectPage.editPage.setText(SharedPrefs.instance[keyPagePresent, String::class.java]) builder.setView(dialogSelectPage) mAlertDialog = builder.show() }
- Khởi tạo Spinner
open fun initSpinner() { initSpinner(null) } open fun initSpinner(params: String?) { if (!checkErrorNetwork(context)) return showProgressDialog() if (TextUtils.isEmpty(params)) { FeedBarAsyncTask(this).execute(getLink(mPosition)) } else { FeedBarAsyncTask(this).execute(getLink(mPosition), params) } }
- Function showProgressDialog khi đang load data
open fun showProgressDialog() { if (mProgressDialog.isShowing) { mProgressDialog.dismiss() } mProgressDialog.show() }
- Function loadData
open fun loadData(url: String) { loadData(url, "") } open fun loadData(url: String, page: String) { if (!checkErrorNetwork(context)) return showProgressDialog() }
- Hiển thị trang hiện tại trên tổng số trang
open fun getPagePresent(pagePresentStr: String, pageMaxStr: String): String { return pagePresentStr + "/" + pageMaxStr }
- Function abstract fun getLink(type: Int): String để lấy link url
3.1. PostFragment.kt
- Full code : PostFragment.kt
- Lấy url cần để load data
override fun getLink(type: Int): String { return when (type) { 0 -> baseUrlViblo 1 -> baseUrlSeries 2 -> baseUrlEditorsChoice 3 -> baseUrlTrending 4 -> baseUrlVideos else -> baseUrlViblo } }
- Khởi tạo RecyclerView với adapter PostAdapter.kt
private fun initRecyclerPost() { mPostAdapter = PostAdapter(context, mPostList, this, this) recyclerPost.adapter = mPostAdapter recyclerPost.layoutManager = LinearLayoutManager(context) }
- Cập nhât recycler view khi dữ liệu từ interface OnUpdatePostData.kt trả về
override fun onUpdatePostData(postList: List<Post>?) { if (postList != null) { mPostList.clear() mPostList.addAll(postList) mPostAdapter.notifyDataSetChanged() } mProgressDialog.dismiss() updateViewNextBackBottom() }
3.2 fragment_post.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_awidth="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.asia.viblo.view.fragment.post.PostFragment"> <Spinner android:id="@+id/spinnerPost" android:layout_awidth="@dimen/size_170" android:layout_height="@dimen/size_30" android:layout_margin="@dimen/size_10" android:visibility="invisible"/> <android.support.v7.widget.RecyclerView android:id="@+id/recyclerPost" android:layout_awidth="match_parent" android:layout_height="0dp" android:layout_weight="1"/> <include android:id="@+id/viewNextBack" layout="@layout/include_layout_next_back_page"/> </LinearLayout>
- Full code : LoadPostAsyncTask.kt
4.1. Lưu vị trí trang hiện tại và trang lớn nhất
try { val document = Jsoup.connect(baseUrl + page).get() val elements = document?.select(getCssQuery(baseUrl, TypeQuery.PAGE)) elements!! .map { it.select("li") } .forEach { data -> data.asSequence() .map { it.getElementsByTag("a").text() } .filterNotTo(pageList) { TextUtils.isEmpty(it) } } } catch (ex: Exception) { ex.printStackTrace() } SharedPrefs.instance.put(keyMaxPage, if (pageList.isNotEmpty()) pageList.last() else "0") if (params.size == 1) { SharedPrefs.instance.put(keyPagePresent, "1") }
4.2. Dùng interface để trả dữ liệu về PostFragment.kt
override fun onPostExecute(result: List<Post>?) { super.onPostExecute(result) mOnUpdatePostData.onUpdatePostData(result) }
4.3. Function getLinkPage()
- Lấy ra trang cần load data nếu
- trang truyền vào từ params < pageMax thì lấy trang đó
- trang truyền vào từ params > pageMax thì lấy pageMax
private fun getLinkPage(baseUrl: String?, page: String?): String { var pageCheck = page try { if (page != null) { val pageMaxStr = SharedPrefs.instance[keyMaxPage, String::class.java] if (!TextUtils.isEmpty(pageMaxStr)) { val pageMax = pageMaxStr.toInt() val pagePresent = page.toInt() if (pagePresent > pageMax) { pageCheck = pageMaxStr } } } } catch (ex: Exception) { ex.printStackTrace() } if (!TextUtils.isEmpty(pageCheck)) { SharedPrefs.instance.put(keyPagePresent, pageCheck) } return when (baseUrl) { baseUrlViblo -> "/?page=" else -> "?page=" } + pageCheck }
5.1. BaseModel.kt
package com.asia.viblo.model import java.io.Serializable /** * Created by FRAMGIAvu.tuan.anh on 09/11/2017. */ open class BaseModel : Serializable { var avatar = "" var name = "" var time = "" var authorUrl = "" var title = "" var score = "" var views = "" var comments = "" var tags: MutableList<String> = arrayListOf() var tagUrlList: MutableList<String> = arrayListOf() }
5.2. Post.kt
package com.asia.viblo.model.post import com.asia.viblo.model.BaseModel /** * Created by FRAMGIAvu.tuan.anh on 27/10/2017. */ open class Post : BaseModel() { var postUrl = "" var clips = "" var posts = "" var reputation = "" var followers = "" var post = "" var isVideo = false }
Github
https://androidcoban.com/su-dung-thu-vien-jsoup-boc-html-trong-android.html https://viblo.asia/ https://kotlinlang.org/docs/reference/
https://www.mediafire.com/file/5ln4272ix13w6nv/viblo.v24.11.2017.apk