12/08/2018, 16:28

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
}

Hình 2

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

0