Xây dựng ứng dụng Viblo trên android bằng kotlin sử dụng lib jsoup (Phần 1)
Bài đăng này đã không được cập nhật trong 3 năm
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. MainActivity
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_width="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_width="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_width="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
}
2. BaseFragment
- 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. PostFragment
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_width="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_width="@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_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<include
android:id="@+id/viewNextBack"
layout="@layout/include_layout_next_back_page"/>
</LinearLayout>
4. LoadPostAsyncTask
- 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. Model
5.1. BaseModel.kt
package com.asia.viblo.model
import java.io.Serializable
/**
* Created by FRAMGIA\vu.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 FRAMGIA\vu.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
}
Hình ảnh
Code
Tài liệu tham khảo
https://androidcoban.com/su-dung-thu-vien-jsoup-boc-html-trong-android.html https://viblo.asia/ https://kotlinlang.org/docs/reference/
Link apk
https://www.mediafire.com/file/5ln4272ix13w6nv/viblo.v24.11.2017.apk
All rights reserved