实现一个简单的拉取网络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:

相关推荐
隔窗听雨眠6 分钟前
从行为引导到基础设施约束:AGI代理风险缓解的工程化路径
网络·人工智能·agi
pt104314 分钟前
Cisco设备网络安全系列-16:微服务、Kubernetes与无服务器架构安全
网络·安全·微服务·架构
SatanII43 分钟前
Kubernetes 网络
网络·容器·kubernetes
青瓦梦滋9 小时前
WebSocket协议
linux·服务器·网络·c++·websocket·网络协议·http
zhang1338308907510 小时前
传统机械三杯式风速传感器易磨损冻堵?CG-88金属超声波风速风向传感器给出免运维方案
运维·服务器·网络·人工智能·自动化
知已难寻10 小时前
第 2 篇 · 云的地基:服务器、存储、网络三大基础设施
运维·服务器·网络·云原生·云计算·负载均衡·1024程序员节
IpdataCloud12 小时前
批量查IP的API接口怎么选?免费API配额瓶颈与离线库本地批量方案实测
网络·网络协议·tcp/ip
ITyunwei098714 小时前
售后夜间值守怎么落地:从告警到交接的工单系统配置笔记
网络
Smoothcloud润云14 小时前
GPU远程纳管安全吗?最小权限、网络边界与日志审计实战
服务器·网络·人工智能·安全·gpu算力·gpu
ai大模型-探索者14 小时前
Function Call与MCP(区别、联系与实战)
java·服务器·网络