api接口地址:https://newsapi.org/docs/get-started
项目成品地址:https://github.com/RushHan824/NewsApiDemo
项目效果展示:

MVVM数据流

UML图

本系列文章将带你从零实现一个新闻列表App,适合零基础读者。一步步来,力求让每个人都能跟着做出来。
第一步:分析API和JSON,写好数据类。
- 打开API网址,获取返回的JSON数据。

- 观察JSON结构,分析每个字段。
这里注意会有null的字段 所以kotlin中创建变量的时候需要注意

- 用Kotlin写出对应的数据类,为后续网络请求和数据展示打好基础。
创建一个数据类 data class
然后根据上图的json结构 把数据类一层一层构建出来 还是比较简单的 结果如下图
当然要注意上面说到的null字段问题 而kotlin中val搭配问好?比较好
比如:val title: String? 就表示可能会null 安全且规范

第二步:配置Retrofit,完成网络请求
1.添加依赖
在build.gradle中添加Retrofit和Gson等依赖。
如下图 注意是app的.gradle文件 依赖可以一起添加了

Kotlin
//实现网络请求的第一步:加上retrofit和gson的依赖
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
//livedata viewmodel依赖
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.6.2")
//Glide 图片加载
implementation("com.github.bumptech.glide:glide:4.16.0")
annotationProcessor("com.github.bumptech.glide:compiler:4.16.0")
2.定义api接口
创建接口,声明请求方法和返回类型。


Kotlin
interface Top_ApiService {
@GET("top-headlines")
suspend fun get_topNews(
@Query("country") country: String,
@Query("apiKey") apiKey: String
):News_Items
}
//我们访问一个网页需要网址 组成可以是baseurl+路径+参数
//这里@GET就是一种请求方式 表示从网页里拿数据 括号里"top-headlines"可以表示具体的路径
//suspend挂起 和kotlin中协程一起搭配 不会阻塞主线程 执行网络请求这样的耗时操作的时候 可以避免界面卡顿
//接下去两个Query就是两个参数
//最后返回类型是数据类
//返回类型通常是我们根据JSON结构定义的数据类(如News_Items),Retrofit会自动帮我们把JSON解析成这个类的对象
3.配置RetrofitClient
Kotlin
object RetrofitClient {//object关键字声明了一个单例对象 这样RetrofitClient在全局只有一个实例 方便全局调用 不用每次都新建
private val client = OkHttpClient.Builder()//OkHttpClient 是Retrofit底层用来发网络请求的库
.addInterceptor(Interceptor { chain ->
val request = chain.request().newBuilder()
.header("User-Agent", "Mozilla/5.0 (Android)")
.build()
chain.proceed(request)//表示继续执行请求
})//这里用Builder()自定义了一个OkHttpClient 加了一个拦截器Interceptor
//拦截器可以在请求发出前、响应返回后做一些统一处理
//这里的拦截器给每个请求都加上了一个User-Agent请求头(模拟浏览器或App身份,有些API会校验这个) 对于现在用的api 不加这个拦截器不行
.build()
val api: Top_ApiService by lazy {//懒加载的属性 只有第一次用到时才会初始化 节省资源
Retrofit.Builder()
.baseUrl("https://newsapi.org/v2/")//设置api基础网址
.addConverterFactory(GsonConverterFactory.create())//用gson把json自动转换成kotlin对象
.client(client)
.build()//创建retrofit对象
.create(Top_ApiService::class.java)//创建API接口的实现对象
}
}
第三步:MVVM架构与数据流转
1.Repository层

Kotlin
class Repository(private val api: Top_ApiService){//数据仓库
//作用是在这里写一下api调用方法的具体实现
suspend fun getTopNews(country: String,apiKey: String):News_Items{
return api.get_topNews(country,apiKey)
}
}
//在MVVM架构中 Repository负责从网络或本地数据库获取数据 并将数据提供给ViewModel 这样可以让ViewModel只关注数据的展示逻辑 而不用关心数据是怎么来的
//getTopNews方法:调用API接口获取新闻数据,参数和返回值都和API接口保持一致。用suspend修饰,表示要在协程中调用,避免阻塞主线程
2.ViewModel层

Kotlin
class NewsViewModel(private val repository: Repository):ViewModel(){
private val _newsList = MutableLiveData<News_Items>()
val newsList:LiveData<News_Items> = _newsList
fun fetchTopNews(country:String,apikey:String){
viewModelScope.launch{
try{
val result=repository.getTopNews(country,apikey)
_newsList.value=result
}catch(_:Exception){ }
}
}
}
//ViewModel:用于管理界面数据,保证数据在界面旋转等情况下不会丢失。
// 比如你在看新闻列表,突然手机横屏了,Activity会被销毁再重建。
// 如果你把数据直接写在Activity里,界面重建后,数据会丢失,需要重新请求。
//ViewModel的作用就是:即使界面重建,ViewModel里的数据还在,不会丢失,这样用户体验更好。
//LiveData/MutableLiveData:实现数据的观察者模式,界面可以自动感知数据变化并刷新。
//viewModelScope.launch:在ViewModel自带的协程作用域中启动协程,进行异步操作(如网络请求),不会阻塞主线程。
//异常捕获:防止网络请求失败导致程序崩溃,可以在catch里加上错误提示
3.Activity层
Kotlin
class NewsFeedActivity : AppCompatActivity() {
private lateinit var viewModel: NewsViewModel
private lateinit var adapter: NewsAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_news_feed)
val recyclerView = findViewById<RecyclerView>(R.id.newsRecyclerView)
recyclerView.layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)//设置布局管理器 瀑布流
adapter = NewsAdapter()//创建适配器
recyclerView.adapter = adapter//绑定适配器
val repository = Repository(RetrofitClient.api)//创建数据仓库对象
viewModel = NewsViewModel(repository)//延迟初始化viewmodel
viewModel.newsList.observe(this) { newsItems ->
adapter.submitList(newsItems.articles)
}
// 通过 observe 订阅 newsList 数据
// 当新闻数据发生变化时 自动调用 lambda 表达式 把新数据提交给 Adapter 刷新界面
viewModel.fetchTopNews("us", "08928b7fed414010820d9af990c05f89")
}
}
第四步:RecyclerView展示新闻列表
1.编写Adapter

Kotlin
class NewsAdapter : RecyclerView.Adapter<NewsAdapter.NewsViewHolder>() {
private var articles: List<Article> = emptyList()
fun submitList(list: List<Article>) {//提交
articles = list
notifyDataSetChanged()//通知recyclerview 数据发生变化 刷新一下
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NewsViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_news, parent, false)
return NewsViewHolder(view)
}//复制粘贴
override fun onBindViewHolder(holder: NewsViewHolder, position: Int) {
holder.bind(articles[position])
}//当前位置数据绑定
override fun getItemCount(): Int = articles.size//告诉recyclerview有多少条数据
class NewsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {//
private val titleTextView: TextView = itemView.findViewById(R.id.newsTitle)
private val imageView: ImageView = itemView.findViewById(R.id.newsImage)
fun bind(article: Article) {//将article数据绑定到控件上
titleTextView.text = article.title
Glide.with(itemView.context).load(article.urlToImage).into(imageView)//Glide高效加载网络图片,防止界面卡顿
}
}
}
//NewsAdapter:是 RecyclerView 的"桥梁",负责把新闻数据展示到每一行 item 上
//onCreateViewHolder:每当需要一个新的 item 行时,加载 item_news.xml 布局,生成 ViewHolder
//onBindViewHolder:把对应位置的数据绑定到 ViewHolder 上
//NewsViewHolder:用来缓存和管理 item_news.xml 里的控件,避免重复查找
2.编写item布局

