思考怎么去做一个网络api的调取, 然后展示到android前端
需求分析
考虑几个页面,页面展示什么内容
我需不需要更改后端内容, 如果只是单纯的获取数据,那就只需考虑GET data
如果我需要更改后端数据库的内容, 那么什么参数和什么事件将会导致我这样做
考虑怎么配置网络请求, 网络请求返回的数据要不要存入到数据库
如果要存入到数据库,那么数据项目的配置是怎么样的
没有网络的情况下还能不能直接去拿到已经存在数据库的内容
考虑适配的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后, 启动后端
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
效果截图
ref link: