实现一个简单的拉取网络todo app

思考怎么去做一个网络api的调取, 然后展示到android前端

需求分析

  1. 考虑几个页面,页面展示什么内容
  2. 我需不需要更改后端内容, 如果只是单纯的获取数据,那就只需考虑GET data
  3. 如果我需要更改后端数据库的内容, 那么什么参数和什么事件将会导致我这样做
  4. 考虑怎么配置网络请求, 网络请求返回的数据要不要存入到数据库
  5. 如果要存入到数据库,那么数据项目的配置是怎么样的
  6. 没有网络的情况下还能不能直接去拿到已经存在数据库的内容
  7. 考虑适配的android平台, 这里以android 13 api 33 为例

实现一个从网络请求获取todo数据,展示在app上面

先任务简单化(注意使用到viewbinding,可能部分代码需要配合xml文件名具体分析)

groovy 复制代码
// 依赖
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'

implementation 'com.drakeet.multitype:multitype:4.3.0'
def activity_version = "1.9.2"
// Kotlin
implementation "androidx.activity:activity-ktx:$activity_version"

def room_version = "2.4.2"
//room
implementation "androidx.room:room-ktx:$room_version"
implementation "androidx.room:room-runtime:$room_version"
//    kapt "androidx.room:room-compiler:$room_version"
ksp "androidx.room:room-compiler:$room_version"

// 奔溃阻止
implementation 'com.github.alhazmy13:Catcho:v1.1.0'

// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1"


implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.3'
implementation 'com.squareup.okhttp3:okhttp:4.9.3'
implementation 'com.google.code.gson:gson:2.8.9'

// ksp插件配置, app模块
plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.kotlin.android)

//    id 'kotlin-kapt'
    id 'com.google.devtools.ksp'
}

// 项目根
plugins {
    alias(libs.plugins.android.application) apply false
    alias(libs.plugins.kotlin.android) apply false
    // https://mvnrepository.com/artifact/com.google.devtools.ksp/symbol-processing
    //runtimeOnly 'com.google.devtools.ksp:symbol-processing:1.9.24-1.0.20'
    id 'com.google.devtools.ksp' version '1.9.24-1.0.20' apply false
}

1.配置BASE_URL

2. 配置好mongodb connect string后, 启动后端

  • go run main.go

3. 找到需要的接口

  • GET api/todos 是我需要的
  • 接口返回示例
json 复制代码
HTTP/1.1 200 OK
Date: Fri, 21 Feb 2025 07: 58: 10 GMT
Content-Type: application/json
Content-Length: 418
Vary: Origin
Connection: close

[
{
"_id": "67b8244a6d9be942d1ba3a8a",
"completed": true,
"body": "hello world"
},
{
"_id": "67b825616d9be942d1ba3a8b",
"completed": true,
"body": "Damn"
},
{
"_id": "67b828926d9be942d1ba3a8c",
"completed": true,
"body": "哈哈"
},
{
"_id": "67b829796d9be942d1ba3a8d",
"completed": true,
"body": "AAA"
},
{
"_id": "67b829896d9be942d1ba3a8e",
"completed": true,
"body": "你好呀"
},
{
"_id": "67b82da36d9be942d1ba3a8f",
"completed": true,
"body": "及你太美"
}
]

4. 编写接口

kotlin 复制代码
interface TodoApi {
    @GET("api/todos")
    suspend fun getAllTodos(): Response<List<Todo>>
}

5. 编写retrofit客户端

kotlin 复制代码
object ApiService {
    // todo: 如果是retrofit 的话,这个连接不生效就马上闪退
    // 后端链接:  https://gitee.com/EEPPEE_admin/probe-examples/tree/master/fiber-and-react-example/backend
    // 配合内网穿透工具cpolar使用
    // cmd: cpolar 6969
    private const val BASE_URL = "https://7ce074a2.r7.cpolar.top"

    val todoApi: TodoApi by lazy {
        Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(TodoApi::class.java)
    }
}

6. 数据库内容项保存

kotlin 复制代码
// 本机要存的信息
@Entity(
    tableName = "todos",
)
data class Todo(
    @PrimaryKey(autoGenerate = true)
    val id: Int = 0,
//    @PrimaryKey(autoGenerate/ = false)
    val _id: String = "",
    val completed: Boolean,
    val body: String,
)

7.dao查询接口

kotlin 复制代码
@Dao
interface TodoDao {
    @Query("SELECT * FROM todos")
    suspend fun getAllTodos(): List<Todo>
}

8. 数据库需要

kotlin 复制代码
@Database(
    entities = [
        Todo::class
    ],
    version = 1,
    exportSchema = false
)
abstract class TodoDB : RoomDatabase() {
    abstract fun todoDao(): TodoDao

    companion object {
        @Volatile
        private var instance: TodoDB? = null

        fun getDatabase(context: Context): TodoDB {
            return instance ?: Room.databaseBuilder(
                context.applicationContext,
                TodoDB::class.java,
                "todo_database"
            ).build()
            instance = instance
            instance
        }
    }
}

9. viewmodel层怎么搞,我也不会

kotlin 复制代码
class TodoViewModel(
    // 1. 需要注入网络api
    private val api: TodoApi,
    // 2. 需要注入本机数据库
    private val db: TodoDB
) : ViewModel() {
    fun getAllTodos() {
        viewModelScope.launch {
            val response = api.getAllTodos()
            Log.d("GET返回", response.body().toString())
            if (response.isSuccessful && response.body() != null) {
                // 拿到api的返回,然后存入数据库
                Log.d("GET响应", response.body().toString())
                response.body()!!.forEach { it ->
                    // 实际交付到dao做实际插入
                    db.todoDao().insertTodo(it)
                }
            }
        }
    }
}

// 还需要一个工厂?

10. 造一个activity和xml文件

  • TodoActivity.kt
  • activity_todo.xml: 需要刷新layout,和recyclerview
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
    xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/swiperefreshlayout"
    android:layout_width="match_parent" android:layout_height="match_parent"
    android:orientation="vertical">


    <androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerView"
        android:layout_width="match_parent" android:layout_height="match_parent" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
  • item_todo.xml: recyclerview里面的数据项
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="wrap_content"
    android:orientation="vertical" android:padding="16dp">

    <TextView android:id="@+id/tvTitle" android:layout_width="wrap_content"
        android:layout_height="wrap_content" android:text="Todo Title" android:textSize="18sp" />

    <TextView android:id="@+id/tvCompleted" android:layout_width="wrap_content"
        android:layout_height="wrap_content" android:text="Completed" android:textSize="14sp" />
</LinearLayout>

11. 为recyclerview编写一个adapter,这里使用MultiType库

kotlin 复制代码
class TodoItemViewBinder : ItemViewBinder<Todo, TodoItemViewBinder.ViewHolder>() {
    // 内部类
    inner class ViewHolder(
        private val binding: ItemTodoBinding // 绑定到item_todo.xml
    ) : RecyclerView.ViewHolder(binding.root) {
        fun bind(todo: Todo) {
            // 绑定效果
            binding.tvTitle.text = todo.body
            binding.tvCompleted.text = if (todo.completed) "已完成" else "暂时没搞定"
        }
    }

    override fun onBindViewHolder(holder: ViewHolder, item: Todo) {
        holder.bind(item)
    }

    override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): ViewHolder {
        val binding = ItemTodoBinding.inflate(inflater, parent, false)
        return ViewHolder(binding)
    }
}

12. 来实操TodoActivity.kt, 始终应该继承app compat activity,主要看onCreate内容就行

kotlin 复制代码
class TodoActivity : AppCompatActivity() {
    // 需要一个MultiTypeAdapter 对象
    private var multitypeAdapter = MultiTypeAdapter()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 这里使用到viewbinding内容, 绑定到activity_todo.xml
        binding = ActivityTodoBinding.inflate(layoutInflater)
        // 配置奔溃阻止,使用到BuildConfig,需要配置build.gradle
        if (!com.example.myapplication.BuildConfig.IS_DEBUG) {
            setUpCatcho(this)
        }
        setContentView(binding.root)

        // 默认就加载网络数据
        loadTodos()

        // 实现下拉刷新,加载网络数据
        binding.swiperefreshlayout.setOnRefreshListener {
            loadTodos()
//            binding.swiperefreshlayout.isRefreshing = false
        }


        binding.recyclerView.layoutManager = LinearLayoutManager(this)
        binding.recyclerView.adapter = multitypeAdapter

        // 注册 Todo 类型及其 ItemViewBinder
        multitypeAdapter.register(Todo::class.java, TodoItemViewBinder())
    }
}

13.抽离出loadTodos,设置一个私有函数(没调试成功)

kotlin 复制代码
  private fun loadTodos() {
    MainScope().launch {
        try {
            if (NetworkCheckConnUtil.isNetworkAvailable(this@TodoActivity)) {
                // 网络可用
                val response = ApiService.todoApi.getAllTodos()
                if (response.isSuccessful && response.body() != null) {
                    val todos = response.body()!!
                    // 插入到数据库
                    todos.forEach { it ->
                        todoDB?.todoDao()
                            ?.insertTodo(it)
                    }

                    multitypeAdapter.items = todos
                    multitypeAdapter.notifyDataSetChanged()
                    binding.recyclerView.adapter = multitypeAdapter
                }
            } else {
                Toast.makeText(this@TodoActivity, "网络不可用", Toast.LENGTH_SHORT)
                    .show()
                loadTodosFromDatabase()
            }
        } catch (e: Exception) {
            e.printStackTrace()
            Toast.makeText(this@TodoActivity, "网络不可用", Toast.LENGTH_SHORT)
                .show()
            loadTodosFromDatabase()
        } finally {
            binding.swiperefreshlayout.isRefreshing = false
        }
    }
}

遇到的问题(以上内容有修改了,看源码为主)

1. retrofit无网络,或者base url配置错时, 程序会闪退,这个东西要额外在viewmodel层处理

  • ok,解决bug的逻辑是这样,一开始就加载本地数据库内容(然后土司网络不可用),知道下滑刷新才加载网络内容,how
    about that
  • 程序一开始就没网的时候就闪退,我解决不了

总结

  • 实现刷新加载网络数据成功
  • 但是没有实现从数据库中获取内容,调试不通,无网络时,会报错误
bash 复制代码
java.net.UnknownHostException: Unable to resolve host "7ce074a2.r7.cpolar.top": No address associated with hostname
at 
  • 写shitcode啊,写安卓没有思路

效果截图

ref link:

相关推荐
码刘的极客手记1 分钟前
vSphere 4.1 隐藏技术全解析:esxcli API 调用、Kickstart 部署优化及 DCUI 界面定制
服务器·网络·esxi·vmware·虚拟机
ai_xiaogui2 分钟前
【网络踩坑】Tailscale开启子网路由(Subnet)导致局域网服务“假死”?深度解析路由优先级与DDNS共存方案
网络
BHXDML4 分钟前
计算机网络实验:(五)路由协议的配置
网络·计算机网络·智能路由器
佟以冬9 分钟前
Wireshark抓包基础
网络·测试工具·安全·网络安全·wireshark
郝学胜-神的一滴14 分钟前
深入Linux网络编程:accept函数——连接请求的“摆渡人”
linux·服务器·开发语言·网络·c++·程序人生
刘一说16 分钟前
Java中基于属性的访问控制(ABAC):实现动态、上下文感知的权限管理
java·网络·python
一起养小猫20 分钟前
Flutter for OpenHarmony 实战:网络监控登录系统完整开发指南
网络·flutter·harmonyos
ZHANG13HAO31 分钟前
android13 4G网络环境和wifi内网说明
linux·服务器·网络
CS创新实验室38 分钟前
《计算机网络》深入学:自治系统 (Autonomous System)
网络·计算机网络
酣大智41 分钟前
FTP--文件传输协议
运维·网络·网络协议·tcp/ip·华为