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

相关推荐
技术不好的崎鸣同学11 分钟前
Windows 命令提示符(CMD)恶意脚本分析篇
网络·windows·安全·系统安全
m0_7156744318 分钟前
2026年医疗行业数据库风险监测产品综合能力排名分析
网络·数据库
阿米亚波2 小时前
【EVE-NG 实战】防火墙基础(VLAN · VRRP · NAT · ACL · 静态路由)
运维·网络·笔记·网络协议·计算机网络·网络安全·运维开发
零意@2 小时前
海光平台debian11的系统,打开pstore功能和验证功能
网络
blueman88882 小时前
qt5-serialbus详细解析
开发语言·网络·qt
学究天人3 小时前
数学公理体系大全:Comprehensive Collection of Mathematical Axiom Systems(卷6)
网络·算法·数学建模·动态规划·几何学·图论·拓扑学
tiantianuser3 小时前
NVME-oF IP 设计2 :设计之前的调研!
网络·网络协议·rdma·roce v2·nvme of
星恒讯工业路由器4 小时前
工业无线通信选型常见问题解答
网络·信息与通信·无线ap·设备选型·工业无线通信·防爆无线设备·4g/5g路由器
智慧光迅AINOPOL4 小时前
校园全光网建设成本对比:全光网取代传统校园网建设成本分析
网络·全光网解决方案·全光网·校园全光网·校园全光网解决方案
2401_873479404 小时前
如何识别代理IP和伪装流量?用IP情报工具三步穿透住宅代理伪装
网络·数据库·网络协议·tcp/ip·ip