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

相关推荐
搬码临时工9 分钟前
使用自定义固定公网URL地址远程访问公司内网OA办公系统,本地无需公网IP和专线让外网访问
网络·网络协议·tcp/ip
星马梦缘2 小时前
计算机网络6 第六章 应用层——解决“怎么发请求、怎么回响应”的问题(邮件整体传输流程)
网络·计算机网络·域名·ftp·dns·dhcp
@CLoudbays_Martin112 小时前
为什么动态视频业务内容不可以被CDN静态缓存?
java·运维·服务器·javascript·网络·python·php
东哥说-MES|从入门到精通3 小时前
Mazak MTF 2025制造未来参观总结
大数据·网络·人工智能·制造·智能制造·数字化
sheepwjl4 小时前
《嵌入式硬件(三):串口通信》
网络·嵌入式硬件·网络协议·串口通信
Jayyih4 小时前
嵌入式系统学习DAY28(网络编程)
网络·学习·tcp/ip
dbdr09014 小时前
Linux 入门到精通,真的不用背命令!零基础小白靠「场景化学习法」,3 个月拿下运维 offer,第二十六天
linux·运维·服务器·网络·python·学习
日更嵌入式的打工仔6 小时前
PHY的自适应协商简析
网络·嵌入式硬件·自适应·phy
XXYBMOOO7 小时前
Qt UDP 通信类详解与实现
开发语言·网络·c++·qt·网络协议·ui·udp
Jayyih7 小时前
嵌入式系统学习Day29(tcp)
网络·学习·tcp/ip