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> = _newsListfun 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: NewsViewModelprivate lateinit var adapter: NewsAdapteroverride 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)//延遲初始化viewmodelviewModel.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 = listnotifyDataSetChanged()//通知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.titleGlide.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布局