当前位置: 首页 > article >正文

Android MVVM demo(使用DataBinding,LiveData,Fresco,RecyclerView,Room,ViewModel 完成)

使用DataBinding,LiveData,Fresco,RecyclerView,Room,ViewModel 完成

玩Android 开放API-玩Android - wanandroid.com

接口使用的是下面的两个:

https://www.wanandroid.com/banner/jsonicon-default.png?t=O83Ahttps://www.wanandroid.com/banner/json

wanandroid.com/project/list/1/json?cid=294icon-default.png?t=O83Ahttps://www.wanandroid.com/project/list/1/json?cid=294

需求列表:

现在我需要使用kotlin 来完成下面的需求,使用mvvm的模式,要求使用databinding,viewmodel,livedata,fresco,room,retrofit来完成

1:主页显示https://www.wanandroid.com/banner/json 的数据
使用recyclerView 来展示每一个banner,其中banner内部使用databinding
使用fresco 来加载对应的图片
使用retrofit 来下载数据,下载后要求数据保存在room里面,如果数据在数据库中没有,那么就添加,如果已经有了就更新当前的数据,primarykey 是id
使用viewmode 

有一个按钮“刷新banner” 会再次拉取https://www.wanandroid.com/banner/json  来刷新,使用databinding来刷新

2:主页还要显示https://www.wanandroid.com/project/list/1/json?cid=294的数据
使用recyclerView 来展示每一个project,其中project内部使用databinding
使用fresco 来加载对应的图片
使用retrofit 来下载数据,下载后要求数据保存在room里面,如果数据在数据库中没有,那么就添加,如果已经有了就更新当前的数据,primarykey 是id
使用viewmode 

有一个按钮“刷新banner” 会再次拉取 https://www.wanandroid.com/project/list/1/json?cid=294 来刷新,使用databinding来刷新



1:https://www.wanandroid.com/banner/json
数据格式如下:
{
  "data": [
    {
      "desc": "我们支持订阅啦~",
      "id": 30,
      "imagePath": "https://www.wanandroid.com/blogimgs/42da12d8-de56-4439-b40c-eab66c227a4b.png",
      "isVisible": 1,
      "order": 2,
      "title": "我们支持订阅啦~",
      "type": 0,
      "url": "https://www.wanandroid.com/blog/show/3352"
    }
  ],
  "errorCode": 0,
  "errorMsg": ""
}

2:https://www.wanandroid.com/project/list/1/json?cid=294
数据格式如下:
{
  "data": {
    "curPage": 1,
    "datas": [
      {
        "adminAdd": false,
        "apkLink": "",
        "audit": 1,
        "author": "qianyue0317",
        "canEdit": false,
        "chapterId": 294,
        "chapterName": "完整项目",
        "collect": false,
        "courseId": 13,
        "desc": "玩Android flutter版本",
        "descMd": "",
        "envelopePic": "https://www.wanandroid.com/blogimgs/89868c9a-e793-46f3-a239-751246951b7f.png",
        "fresh": false,
        "host": "",
        "id": 27961,
        "isAdminAdd": false,
        "link": "https://www.wanandroid.com/blog/show/3618",
        "niceDate": "2024-01-29 22:10",
        "niceShareDate": "2024-01-29 22:10",
        "origin": "",
        "prefix": "",
        "projectLink": "https://github.com/qianyue0317/wan_android_flutter",
        "publishTime": 1706537457000,
        "realSuperChapterId": 293,
        "selfVisible": 0,
        "shareDate": 1706537457000,
        "shareUser": "",
        "superChapterId": 294,
        "superChapterName": "开源项目主Tab",
        "tags": [
          {
            "name": "项目",
            "url": "/project/list/1?cid=294"
          }
        ],
        "title": "玩Android-flutter项目",
        "type": 0,
        "userId": -1,
        "visible": 1,
        "zan": 0
      }
    ],
    "offset": 0,
    "over": false,
    "pageCount": 20,
    "size": 15,
    "total": 289
  },
  "errorCode": 0,
  "errorMsg": ""
}

0:在应用模块的 build.gradle 文件中,确保启用了数据绑定:

android {
    //...
    buildFeatures {
        dataBinding = true
    }
}

1. 依赖项配置

在项目的 build.gradle 文件中添加以下依赖项:

plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.kotlin.android)
}

android {
    namespace = "com.example.mykotlin"
    compileSdk = 34

    defaultConfig {
        applicationId = "com.example.mykotlin"
        minSdk = 33
        targetSdk = 34
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = "1.8"
    }
    dataBinding{
        enable = true
    }
}

dependencies {
    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.appcompat)
    implementation(libs.material)
    implementation(libs.androidx.activity)
    implementation(libs.androidx.constraintlayout)
    implementation(libs.com.facebook.fresco.fresco4)
    implementation(libs.androidx.lifecycle.livedata.ktx) // 这里的版本号可能需要根据实际情况调整
    implementation(libs.androidx.lifecycle.viewmodel.ktx) // 同上
    implementation(libs.com.squareup.retrofit2.retrofit)
    implementation(libs.com.squareup.retrofit2.converter.gson)
    //noinspection UseTomlInstead
    implementation("androidx.room:room-runtime:2.5.2")
    annotationProcessor("androidx.room:room-compiler:2.5.2")
    testImplementation(libs.junit)
    androidTestImplementation(libs.androidx.junit)
    androidTestImplementation(libs.androidx.espresso.core)
    implementation("androidx.databinding:databinding-runtime:7.4.0")
}

2: 数据模型类

创建数据模型类来表示 Banner 和 Project 的数据:

data class BannerItem(
    val id: Int,
    val desc: String,
    val imagePath: String,
    val title: String,
    val url: String
)

data class ProjectItem(
    val id: Int,
    val title: String,
    val desc: String,
    val envelopePic: String,
    // 其他属性
)

3:数据库相关

创建数据库和 DAO:

@Database(entities = [BannerItem::class, ProjectItem::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun bannerDao(): BannerDao
    abstract fun projectDao(): ProjectDao
}

@Dao
interface BannerDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertBanners(banners: List<BannerItem>)

    @Query("SELECT * FROM BannerItem")
    suspend fun getAllBanners(): List<BannerItem>
}

@Dao
interface ProjectDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertProjects(projects: List<ProjectItem>)

    @Query("SELECT * FROM ProjectItem")
    suspend fun getAllProjects(): List<ProjectItem>
}

4. Retrofit 服务接口

package com.example.mykotlin.model.data

import android.app.Application
import android.util.Log
import androidx.lifecycle.MutableLiveData
import com.example.mykotlin.model.dao.BannerDao
import com.example.mykotlin.model.dao.ProjectDao
import com.example.mykotlin.model.data.network.WanAndroidApi
import com.example.mykotlin.model.db.AppDatabase
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.security.KeyManagementException
import java.security.NoSuchAlgorithmException
import java.security.SecureRandom
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManager
import javax.net.ssl.X509TrustManager


class RetrofitSingleton private constructor() {
    companion object {
        private var instance: retrofit2.Retrofit? = null

        fun getInstance(): retrofit2.Retrofit {
            if (instance == null) {
                synchronized(RetrofitSingleton::class.java) {
                    if (instance == null) {
                        try {
                            val trustAllCerts = arrayOf<TrustManager>(UnsafeTrustManager())
                            val sslContext = SSLContext.getInstance("SSL")
                            sslContext.init(null, trustAllCerts, SecureRandom())

                            val client = okhttp3.OkHttpClient.Builder()
                                .sslSocketFactory(sslContext.socketFactory, trustAllCerts[0] as X509TrustManager)
                                .hostnameVerifier(UnsafeHostnameVerifier())
                                .build()

                            instance = retrofit2.Retrofit.Builder()
                                .baseUrl("https://www.wanandroid.com/")
                                .addConverterFactory(retrofit2.converter.gson.GsonConverterFactory.create())
                                .client(client)
                                .build()
                        } catch (e: NoSuchAlgorithmException) {
                            e.printStackTrace()
                        } catch (e: KeyManagementException) {
                            e.printStackTrace()
                        }
                    }
                }
            }
            return instance!!
        }
    }
}

class UnsafeTrustManager : X509TrustManager {
    override fun checkClientTrusted(chain: Array<out java.security.cert.X509Certificate>?, authType: String?) {}

    override fun checkServerTrusted(chain: Array<out java.security.cert.X509Certificate>?, authType: String?) {}

    override fun getAcceptedIssuers(): Array<java.security.cert.X509Certificate> = arrayOf()
}

class UnsafeHostnameVerifier : javax.net.ssl.HostnameVerifier {
    override fun verify(hostname: String?, sslSession: javax.net.ssl.SSLSession?): Boolean = true
}


class DataRepository(private val application: Application) {
    private val TAG = "DataRepository-Kodulf"
//    val database = AppDatabase.getInstance(application)

//    private val bannerDao: BannerDao = database.bannerDao()
//    private val projectDao: ProjectDao  = database.projectDao()
    val bannerList = MutableLiveData<MutableList<BannerItem>>()
    val projectList = MutableLiveData<List<ProjectItem>>()
    private val executorService = CoroutineScope(Dispatchers.IO)
//    loadBannersFromDatabase()
//    loadProjectsFromDatabase()

    init {


    }

//    private fun loadBannersFromDatabase() {
//        executorService.launch {
//            val banners = bannerDao.getAllBanners()
//            if (banners!= null && banners.isNotEmpty()) {
//                bannerList.postValue(banners)
//            }
//        }
//    }

//    private fun loadProjectsFromDatabase() {
//        executorService.launch {
//            val projects = projectDao.getAllProjects()
//            if (projects!= null && projects.isNotEmpty()) {
//                projectList.postValue(projects)
//            }
//        }
//    }

    fun fetchBanners(){
        val retrofit = Retrofit.Builder()
            .baseUrl("https://www.wanandroid.com/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()

        val apiService = retrofit.create(WanAndroidApi::class.java)
        val call = apiService.getBanners()

        call.enqueue(object : retrofit2.Callback<BannerResponse> {
            override fun onResponse(
                call: Call<BannerResponse>,
                response: retrofit2.Response<BannerResponse>
            ) {
                if (response.isSuccessful) {
                    val bannerResponse = response.body()
                    // 处理返回的数据

                    Log.d(TAG,"Banner network request fetchBanners " + bannerResponse)
                    bannerList.postValue(bannerResponse?.data)
                }
            }

            override fun onFailure(call: Call<BannerResponse>, t: Throwable) {
                // 处理请求失败的情况
                Log.e(TAG,"Banner network request fetchBanners onFailure ",t)
                Log.d(TAG,t.toString())

            }
        })
    }

//    fun fetchBanners2() {
//        Log.d(TAG,"Banner network request fetchBanners")
//        val apiService = RetrofitSingleton.getInstance().create(WanAndroidApi::class.java)
//
//        runBlocking {
//            launch {
//                val data = apiService.getBanners().data
//
//                Log.d(TAG, "Banner network request fetchBanners data = " + data)
//
//                data?.let {
//                    Log.d(TAG, "Banner network request fetchBanners doBannerUpdate")
//
                doBannerUpdate(data)
//                    try {
                bannerDao.insertBanners(bannerItems)
//                        // 更新 LiveData 的值
//                        Log.d(
//                            TAG,
//                            "Banner network request fetchBanners doBannerUpdate bannerList.postValue(bannerItems)"
//                        )
//
//                        bannerList.postValue(data)
//                    } catch (e: Exception) {
//                        println("Error updating or inserting banners: ${e.message}")
//                    }
//                }
//            }
//        }
//    }

    private fun doBannerUpdate(bannerItems: MutableList<BannerItem>) {
        executorService.launch {
            try {
//                bannerDao.insertBanners(bannerItems)
                // 更新 LiveData 的值
                Log.d(TAG,"Banner network request fetchBanners doBannerUpdate bannerList.postValue(bannerItems)")

                bannerList.postValue(bannerItems)
            } catch (e: Exception) {
                println("Error updating or inserting banners: ${e.message}")
            }
        }
    }
//
//    fun fetchProjects() {
//        val apiService = RetrofitSingleton.getInstance().create(WanAndroidApi::class.java)
//        executorService.launch {
//            val data = apiService.getProjects().data;
//            data?.let{
//                doProjectUpdate(data)
//            }
//        }
//    }
//
//    private fun doProjectUpdate(datas: List<ProjectItem>) {
//        executorService.launch {
//            try {
//                projectDao.insertProjects(datas)
//                // 更新 LiveData 的值
//                projectList.postValue(datas)
//            } catch (e: Exception) {
//                println("Error updating or inserting projects: ${e.message}")
//            }
//        }
//    }
}

5:ViewModel

package com.example.mykotlin.viewmodel

import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.example.mykotlin.model.data.BannerItem
import com.example.mykotlin.model.data.DataRepository

//注意这里application 前面没有var
class HomeViewModel(application: Application) : AndroidViewModel(application) {

    init {

    }
    private val dataRepository:DataRepository = DataRepository(application)

    private val bannerList:MutableLiveData<MutableList<BannerItem>> = dataRepository.bannerList

    fun refreshBanner(){
        dataRepository.fetchBanners();
    }

    fun getBannerList():MutableLiveData<MutableList<BannerItem>>{
        return bannerList
    }

}

6. 布局文件

创建主布局文件 activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<layout>

    <data>
        <import type="com.example.mykotlin.viewmodel.HomeViewModel"/>
        <variable
            name="viewModel"
            type="HomeViewModel" />

    </data>

    <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:id="@+id/main"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".view.MainActivity">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/jumpXieCheng"
            android:onClick="jumpXieCheng"
            android:text="跳转到协程" />
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/refreshBannerButton"
            android:onClick="refreshBanner"
            android:text="刷新banner" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/refreshProjectButton"
            android:onClick="refreshProject"
            android:text="刷新project" />

        </LinearLayout>
        <View
            android:layout_width="match_parent"
            android:layout_height="10dp"
            android:background="#990000"/>
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/bannerRecyclerView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
        <View
            android:layout_width="match_parent"
            android:layout_height="10dp"
            android:background="#009900"/>
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/projectRecyclerView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

    </LinearLayout>
</layout>

创建 Banner 项布局文件 banner_item.xml

<?xml version="1.0" encoding="utf-8"?>
<layout>
    <data>
        <import type="com.example.mykotlin.model.data.BannerItem"/>
        <variable
            name="banner"
            type="BannerItem" />
    </data>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:orientation="vertical"
        android:layout_height="200dp">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/des"
            android:text="123"/>

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/title"
            android:text="1234"/>
        <View
            android:layout_width="match_parent"
            android:layout_height="10dp"
            android:background="@color/black"/>

    </LinearLayout>
</layout>

创建 Project 项布局文件 project_item.xml

<?xml version="1.0" encoding="utf-8"?>
<layout 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">

    <data>
        <variable
            name="project"
            type="com.example.yourpackage.ProjectItem" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <com.facebook.drawee.view.SimpleDraweeView
            android:id="@+id/projectImage"
            android:layout_width="match_parent"
            android:layout_height="150dp"
            app:srcCompat="@{project.envelopePic}" />

        <TextView
            android:id="@+id/projectTitle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{project.title}"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/projectImage" />

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

7. Activity

package com.example.mykotlin.view

import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.mykotlin.R
import com.example.mykotlin.databinding.ActivityMainBinding
import com.example.mykotlin.model.data.BannerItem
import com.example.mykotlin.viewmodel.HomeViewModel
import com.example.mykotlin.xiecheng.XieChengActivity


class MainActivity : AppCompatActivity() {
    lateinit var activityMainBinding:ActivityMainBinding
    lateinit var homeViewModel:HomeViewModel
    //初始化为空的列表
    var list:MutableList<BannerItem> = mutableListOf()
    var bannerAdapter: BannerAdapter = BannerAdapter(list)

    var TAG:String = "kodulf-Mainactivity"
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);

        homeViewModel = ViewModelProvider(
            viewModelStore,//这个对象只有在Activity创建之和才会有
            defaultViewModelProviderFactory
        ).get(HomeViewModel::class.java)

        homeViewModel.getBannerList().observe(this, Observer { value ->
            Log.d(TAG, "getBannerListLiveData onchanged + $value")
            bannerAdapter.setBanners(value)

        })

        activityMainBinding.bannerRecyclerView.adapter = bannerAdapter
        activityMainBinding.bannerRecyclerView.layoutManager = LinearLayoutManager(this)

    }

    fun jumpXieCheng(view: View) {
        val intent = Intent(this,XieChengActivity::class.java)
        startActivity(intent)
    }

    fun refreshBanner(view:View){
        homeViewModel.refreshBanner()
    }
}

8. 适配器

创建 Banner 适配器和 Project 适配器:

package com.example.mykotlin.view

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.example.mykotlin.R
import com.example.mykotlin.databinding.ItemBannerBinding
import com.example.mykotlin.model.data.BannerItem
class BannerAdapter(private val banners: MutableList<BannerItem>) : RecyclerView.Adapter<BannerAdapter.BannerViewHolder>() {

    class BannerViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        fun bind(bannerItem: BannerItem) {
            // 绑定数据到视图的逻辑
            itemView.findViewById<TextView>(R.id.des).setText(bannerItem.desc)
            itemView.findViewById<TextView>(R.id.title).setText(bannerItem.title)
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BannerViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_banner, parent, false)
        return BannerViewHolder(view)
    }

    override fun onBindViewHolder(holder: BannerViewHolder, position: Int) {
        holder.bind(banners[position])
    }

    override fun getItemCount(): Int {
        return banners.size
    }

    fun setBanners(newBanners: MutableList<BannerItem>) {
        banners.clear()
        banners.addAll(newBanners)
        notifyDataSetChanged()
    }
}


http://www.kler.cn/a/388839.html

相关文章:

  • @ComponentScan:Spring Boot中的自动装配大师
  • 一文简单了解Android中的input流程
  • Kafka - 启用安全通信和认证机制_SSL + SASL
  • 并发基础:(淘宝笔试题)三个线程分别打印 A,B,C,要求这三个线程一起运行,打印 n 次,输出形如“ABCABCABC....”的字符串【举一反三】
  • 使用支付宝沙箱完成商品下单
  • Hadoop(环境搭建篇)
  • 攻防世界35-easyupload-CTFWeb
  • 【国产MCU系列】-GD32F4内存映射
  • 基于springboot+vu的二手车交易系统(全套)
  • 如何在docker创建的mysql容器中执行mysql脚本
  • 《大数据治理》
  • 【LeetCode】【算法】560. 和为 K 的子数组
  • 成都睿明智科技有限公司抖音电商服务效果如何?
  • 欺诈文本分类检测(十八):基于llama.cpp+CPU推理
  • vform2 表单数据回显问题
  • WPF中的ResizeMode
  • 用Vue3+SpringBoot实现餐厅点餐系统的购物车功能
  • 数据库系统概论(期末复习版)
  • 简单叙述 Spring 是如何解决循环依赖问题的呢?
  • ubuntu 22.04 server 安装 mysql 5.7.40 LTS
  • layui xm-select的使用
  • ASP.NET Core 路由规则,自定义特性路由 ,IActionConstraint 路由约束 总结 mvc
  • Swift 开发教程系列 - 第12章:协议与协议扩展
  • 利用RANSAC算法拟合平面并生成包围框的点云处理方法,点云聚类、质心坐标、倾斜角度、点云最小外接矩形
  • 【JAVA】正则表达式的贪婪模式与非贪婪模式
  • 详解MySQL安装