android studio(NewsApiDemo)100%kotlin

发布于:2025-07-24 ⋅ 阅读:(19) ⋅ 点赞:(0)

api接口地址:https://newsapi.org/docs/get-started

项目成品地址:https://github.com/RushHan824/NewsApiDemo

项目效果展示:

 MVVM数据流

 UML图


本系列文章将带你从零实现一个新闻列表App,适合零基础读者。一步步来,力求让每个人都能跟着做出来。

第一步:分析API和JSON,写好数据类。

1. 打开API网址,获取返回的JSON数据。

        

2. 观察JSON结构,分析每个字段。

        这里注意会有null的字段 所以kotlin中创建变量的时候需要注意

3. 用Kotlin写出对应的数据类,为后续网络请求和数据展示打好基础。

        创建一个数据类 data class

        然后根据上图的json结构 把数据类一层一层构建出来 还是比较简单的 结果如下图

        当然要注意上面说到的null字段问题 而kotlin中val搭配问好?比较好

                比如:val title: String? 就表示可能会null 安全且规范


第二步:配置Retrofit,完成网络请求

        1.添加依赖

                在build.gradle中添加Retrofit和Gson等依赖。

                如下图 注意是app的.gradle文件 依赖可以一起添加了

//实现网络请求的第一步:加上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接口

                创建接口,声明请求方法和返回类型。

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                

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层

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层

    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层            

      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

      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布局


      网站公告

      今日签到

      点亮在社区的每一天
      去签到