实现一个简单的拉取网络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 分钟前
IP协议、以太网包头及UNIX域套接字
网络·tcp/ip·unix
小突突突1 小时前
个人博客系统测试报告
运维·网络·功能测试
triticale2 小时前
【Java】网络编程(Socket)
java·网络·socket
wanhengidc2 小时前
服务器中存储空间不足该怎么办?
运维·服务器·网络
soulermax2 小时前
数字ic后端设计从入门到精通4(含fusion compiler, tcl教学)CMOS VLSI Design
网络·硬件架构
bing_1584 小时前
什么是IoT长连接服务?
网络·物联网·长连接服务
christine-rr4 小时前
【25软考网工】第六章(4)VPN虚拟专用网 L2TP、PPTP、PPP认证方式;IPSec、GRE
运维·网络·网络协议·网络工程师·ip·软考·考试
小白自救计划4 小时前
网络协议分析 实验四 ICMPv4与ICMPv6
网络·网络协议
purrrew4 小时前
【Java ee初阶】网络编程 UDP socket
java·网络·网络协议·udp·java-ee
python算法(魔法师版)5 小时前
API安全
网络·物联网·网络协议·安全·网络安全