Android Compose 新闻App(一)网络框架搭建

简介: Android Compose 新闻App(一)网络框架搭建

前言


 要去学习新的知识,光是简单的使用还是不够的,最好是有一个项目让你去了解和学习,在开发中去增加你的使用,并且以后回头来看很快就能用上,哪怕你现在用不上,知识的储备是非常要必要的,能给你的未来更多机会。


正文


 最近觉得Compose很有意思,想要去写一个关于Compose的系列文章,做一个简单的新闻App,话不多说,我们新建一个项目吧。


一、项目创建

8b865438638b4dd0954decc3d1164be2.png


这里选择的是Empty Compose Activity,点击Next。

2a52aa0e6e584739aa8fafa625b13bb7.png


就命名GoodNews吧,开发语言就是Kotlin,我这里用的是当前最新版本的AS,点击Finish完成项目创建。


二、依赖配置


 作为一个新闻App,新闻数据的获取是通过网络API,那么我们需要先构建一个网络框架。之前用Java写网络框架时是通过Okhttp、Retrofit、rxJava、那么在Kotlin中就使用Retrofit和协程来操作,在app的build.gradle的dependencies{}闭包中添加如下代码:

  //Retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    //LiveData、ViewModel
    implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
    implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.0-alpha02'

ef1006f6a7db4879bb6cc2aaab57cf39.png

然后Sync Now。


三、数据API


 现在免费的API数据接口实在太少了,聚合的每天免费次数也只供测试的,因为我重新找了一个API接口,就是天行数据,点击进入完成注册登录以及实名制。


然后我们可以进入我的控制台

f297f9b0cdf64689b8795d1879ea5647.png


点击这里申请接口,这里我选择了一个抗击疫情的接口,点击申请,可以看到这里有免费的调用次数,建议开发者使用自己的Key去调用。

77d24448999f4036966d98e0ca2be29d.png


下面回到我的控制台,然后是我申请的接口,找到抗击疫情,点击立即调试。

bfd76c8988ab4db38d417ef8dc676e60.png


在这里可以看到请求地址和请求参数,我们点击测试请求按钮。

5b580ee0a09a431eb35c6dd478d0382e.png


这里我们就拿到了返回的数据,通过返回的数据去构建Kotlin的Data类。

3a60f46b5eac43e3b20dcb5a15c540a3.png


这里我推荐一个AS插件,很好用,点击File,然后Settings… ,选择Plugins,输入Generate Kotlin data classes from JSON

e31d23bacd984679bdba926b0024f468.png


安装好插件之后,我们来使用它。在com.llw.goodnews包下新建一个bean包,鼠标右键点击Generate class from GSON。

d94dc62b25ab499494c4503b4c294aeb.png


输入数据类名称,然后将JSON格式数据粘贴到下方,点击OK。

f3b496d417e647ec9b6ddb714f2c3706.png


生成了这么多个数据类,我们看一下EpidemicNews

d8039cb1f9b7490eb2e95fc1f257c4a1.png


它里面包裹了一个列表NewslistItem,你看到类都是这种情况,数据是很多的,所以每一层都有一个data类。现在数据有了,下面就是通过这个接口去进行网络请求了。


四、网络框架构建


 做网络请求肯定不能够随便写,要考虑实用性,这个网络框架我也是在《第一行代码》中学到的,建议有些不知道的地方可以看看这本书,这里就拿来用,稍微有一点变化,不过不大。在com.llw.goodnews包下新建一个network包,包下新建一个ServiceCreator类,代码如下:

object ServiceCreator {
    private const val baseUrl = "http://api.tianapi.com"
    private fun getRetrofit() : Retrofit =
        Retrofit.Builder()
            .baseUrl(baseUrl)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    fun <T> create(serviceClass: Class<T>): T = getRetrofit().create(serviceClass)
    inline fun <reified T> create(): T = create(T::class.java)
}


这里的代码就很简单了,通过网络地址构建一个Retrofit,然后根据传入的Service去访问接口,这里还有一个内联函数。


下面我们来构建这个服务接口,在此之前先在com.llw.goodnews包下新建一个utils包,包下新建一个Constant的类,里面的代码如下:

object Constant {
    /**
     * 天行数据Key,请使用自己的Key
     */
    const val API_KEY = "d8bc937c366fcd1629e00f19105db258"
    /**
     * 请求接口成功状态码
     */
    const val CODE = 200
    /**
     * 请求接口成功状态描述
     */
    const val SUCCESS = "success"
}


这里就是一个常量类,我们在请求API接口时会用到的一些不变的值就放这里。然后我们在network包下新建一个ApiService接口,代码如下:

interface ApiService {
    /**
     * 获取新闻数据
     */
    @GET("/ncov/index?key=$API_KEY")
    fun getEpidemicNews(): Call<EpidemicNews>
}


下面我们在network包下新建一个发起请求的NetworkRequest类,代码如下:

object NetworkRequest {
    /**
     * 创建服务
     */
    private val service = ServiceCreator.create(ApiService::class.java)
    //通过await()函数将getNews()函数也声明成挂起函数。使用协程
    suspend fun getEpidemicNews() = service.getEpidemicNews().await()
    /**
     * Retrofit网络返回处理
     */
    private suspend fun <T> Call<T>.await(): T = suspendCoroutine {
        enqueue(object : Callback<T> {
            //正常返回
            override fun onResponse(call: Call<T>, response: Response<T>) {
                val body = response.body()
                if (body != null) it.resume(body)
                else it.resumeWithException(RuntimeException("response body is null"))
            }
            //异常返回
            override fun onFailure(call: Call<T>, t: Throwable) {
                it.resumeWithException(t)
            }
        })
    }
}


这段代码解释一下:首先我们使用ServiceCreator创建了一个ApiService接口的动态代理对象,然后定义了一个getEpidemicNews函数,调用刚刚在ApiService中定义的getEpidemicNews方法,以发起疫情新闻数据请求。这里简化了Retrofit回调的写法,这里定义了一个await()函数,它是一个挂起函数,我们给它声明了一个泛型T,并将await()函数定义成了Call< T >的扩展函数,这样所有返回值是Call类型的Retrofit网络请求接口都可以直接调用await()函数了。

 接着,await()函数中使用了suspendCoroutine函数来挂起当前协程,并且由于扩展函数的原因,我们现在拥有了Call对象的上下文,那么这里就可以直接调用enqueue()方法让Retrofit发起网络请求。


最后我们在存储库中发起数据请求,在com.llw.goodnews下创建一个repository包,包下新建一个BaseRepository,里面的代码如下:

open class BaseRepository {
    fun <T> fire(context: CoroutineContext, block: suspend () -> Result<T>) =
        liveData(context) {
            val result = try {
                block()
            } catch (e: Exception) {
                Result.failure(e)
            }
            //通知数据变化
            emit(result)
        }
}


这里的fire()函数,按照liveData()函数的参数接收标准定义的一个高阶函数。在fire()函数的内部会先调用一下liveData()函数,然后在liveData()函数的代码块中统一进行try catch处理,并在try语句中调用传入的Lambda表达式中的代码,最终Lambda表达式的执行结果并调用emit()方法发射出去。


然后我们在repository包下再新建一个EpidemicNewsRepository类,用于请求疫情新闻数据,继承自BaseRepository,里面的代码如下:

object EpidemicNewsRepository : BaseRepository() {
    fun getEpidemicNews() = fire(Dispatchers.IO) {
        val epidemicNews = NetworkRequest.getEpidemicNews()
        if (epidemicNews.code == CODE) Result.success(epidemicNews)
        else Result.failure(RuntimeException("getNews response code is ${epidemicNews.code} msg is ${epidemicNews.msg}"))
    }
}


 这里我们调用父类的fire()函数,将liveData()函数的线程参数类型指定成了Dispatchers.IO,这样的代码块中的所有代码都是运行在子线程中,如果请求状态码是200,则表示成功,那么就使用Kotlin内置的Result.success()方法来包装获取的疫情新闻数据,然后就调用Result.failure()方法来包装一个异常信息。


那么到这里为止,网络框架就搭建完成了,要使用的话还需要一些配置:


五、项目配置


 这里我们在com.llw.goodnews包下自定义一个App类,继承自Application,代码如下:

class App : Application() {
    companion object {
        @SuppressLint("StaticFieldLeak")
        lateinit var context: Context
    }
    override fun onCreate() {
        super.onCreate()
        context = applicationContext
    }
}


然后因为我们访问的API是http开头的,在Android9.0及以上版本中默认访问https,因此我们需要打开对http的网络访问,在res文件夹下新建一个xml文件夹,在xml文件夹下创建一个network_config.xml,里面的代码如下:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true"/>
</network-security-config>


然后我们在AndroidManifest.xml中去配置,如下图所示:

04078cf3c11a40c8b0e2ef435e4c2651.png


现在万事具备,只差请求了。


六、网络请求


进入到MainActivity,新增如下代码:

          EpidemicNewsRepository.getEpidemicNews().observe(this@MainActivity) { result ->
                        val epidemicNews = result.getOrNull()
                        if (epidemicNews != null) {
                            Log.d("TAG", "onCreate: ${epidemicNews.code}")
                            Log.d("TAG", "onCreate: ${epidemicNews.msg}")
                            Log.d("TAG", "onCreate: ${epidemicNews.newslist?.get(0)?.news?.get(0)?.title}")
                            Log.d("TAG", "onCreate: ${epidemicNews.newslist?.get(0)?.news?.get(0)?.summary}")
                        } else {
                            Log.e("TAG", "onCreate: null")
                        }
                    }

 

添加的位置为下图所示:

e7dcc3b4ddde4822aa00fb5978e7b922.png


这里就是通过请求返回数据,然后打印一下数据,下面来运行一下:

c26a2f4e78464e4f93c8538142c362eb.png


OK,网络框架就没有啥问题了,主要有一个点不爽,就是这里的bean里面太多类,我们写到一个类里面,修改一下EpidemicNews.kt,里面的代码如下:

data class EpidemicNews(val msg: String = "",
                        val code: Int = 0,
                        val newslist: List<NewslistItem>?)
data class NewslistItem(val news: List<NewsItem>?,
                        val desc: Desc,
                        val riskarea: Riskarea)
data class NewsItem(val summary: String = "",
                    val sourceUrl: String = "",
                    val id: Int = 0,
                    val title: String = "",
                    val pubDate: Long = 0,
                    val pubDateStr: String = "",
                    val infoSource: String = "")
data class Desc(val curedCount: Int = 0,
                val seriousCount: Int = 0,
                val currentConfirmedIncr: Int = 0,
                val midDangerCount: Int = 0,
                val suspectedIncr: Int = 0,
                val seriousIncr: Int = 0,
                val confirmedIncr: Int = 0,
                val globalStatistics: GlobalStatistics,
                val deadIncr: Int = 0,
                val suspectedCount: Int = 0,
                val currentConfirmedCount: Int = 0,
                val confirmedCount: Int = 0,
                val modifyTime: Long = 0,
                val createTime: Long = 0,
                val curedIncr: Int = 0,
                val yesterdaySuspectedCountIncr: Int = 0,
                val foreignStatistics: ForeignStatistics,
                val highDangerCount: Int = 0,
                val id: Int = 0,
                val deadCount: Int = 0,
                val yesterdayConfirmedCountIncr: Int = 0)
data class Riskarea(val high: List<String>?,
                    val mid: List<String>?)
data class GlobalStatistics(val currentConfirmedCount: Int = 0,
                            val confirmedCount: Int = 0,
                            val curedCount: Int = 0,
                            val currentConfirmedIncr: Int = 0,
                            val confirmedIncr: Int = 0,
                            val curedIncr: Int = 0,
                            val deadCount: Int = 0,
                            val deadIncr: Int = 0,
                            val yesterdayConfirmedCountIncr: Int = 0)
data class ForeignStatistics(val currentConfirmedCount: Int = 0,
                             val confirmedCount: Int = 0,
                             val curedCount: Int = 0,
                             val currentConfirmedIncr: Int = 0,
                             val suspectedIncr: Int = 0,
                             val confirmedIncr: Int = 0,
                             val curedIncr: Int = 0,
                             val deadCount: Int = 0,
                             val deadIncr: Int = 0,
                             val suspectedCount: Int = 0)


改完之后删除其他的类,只保留一个

6fa22e3426e24b0f806cfeb642a5abcd.png


再运行一下看看效果如何

d2c483ae36574cdea969a1cf6ee6525d.png


OK,木有问题。


七、源码


GitHub:GoodNews

CSDN:GoodNews_1.rar


CSDN的源码rar文件表示的是当前这篇文章的源码,后面即使更新文章,这个源码不会变,而GitHub上的源码是会是最新的源码。


相关文章
|
1月前
|
监控 安全
从 Racket 语言出发,创新员工网络监控软件的框架
在数字化企业环境中,员工网络监控软件对于保障信息安全和提升效率至关重要。Racket 语言凭借其独特特性和强大功能,为开发创新的监控软件提供了新可能。通过捕获和分析网络数据包、记录员工网络活动日志,甚至构建复杂的监控框架,Racket 能够满足企业的定制化需求,为企业信息安全和管理提供强有力支持。未来,基于 Racket 的创新解决方案将不断涌现。
37 6
|
6天前
|
数据采集 存储 JSON
Python网络爬虫:Scrapy框架的实战应用与技巧分享
【10月更文挑战第27天】本文介绍了Python网络爬虫Scrapy框架的实战应用与技巧。首先讲解了如何创建Scrapy项目、定义爬虫、处理JSON响应、设置User-Agent和代理,以及存储爬取的数据。通过具体示例,帮助读者掌握Scrapy的核心功能和使用方法,提升数据采集效率。
41 6
|
14天前
|
机器学习/深度学习 人工智能
类人神经网络再进一步!DeepMind最新50页论文提出AligNet框架:用层次化视觉概念对齐人类
【10月更文挑战第18天】这篇论文提出了一种名为AligNet的框架,旨在通过将人类知识注入神经网络来解决其与人类认知的不匹配问题。AligNet通过训练教师模型模仿人类判断,并将人类化的结构和知识转移至预训练的视觉模型中,从而提高模型在多种任务上的泛化能力和稳健性。实验结果表明,人类对齐的模型在相似性任务和出分布情况下表现更佳。
38 3
|
1月前
|
安全 网络安全 区块链
网络安全与信息安全:构建数字世界的防线在当今数字化时代,网络安全已成为维护个人隐私、企业机密和国家安全的重要屏障。随着网络攻击手段的不断升级,从社交工程到先进的持续性威胁(APT),我们必须采取更加严密的防护措施。本文将深入探讨网络安全漏洞的形成原因、加密技术的应用以及提高公众安全意识的重要性,旨在为读者提供一个全面的网络安全知识框架。
在这个数字信息日益膨胀的时代,网络安全问题成为了每一个网民不可忽视的重大议题。从个人信息泄露到企业数据被盗,再到国家安全受到威胁,网络安全漏洞如同隐藏在暗处的“黑洞”,时刻准备吞噬掉我们的信息安全。而加密技术作为守护网络安全的重要工具之一,其重要性不言而喻。同时,提高公众的安全意识,也是防范网络风险的关键所在。本文将从网络安全漏洞的定义及成因出发,解析当前主流的加密技术,并强调提升安全意识的必要性,为读者提供一份详尽的网络安全指南。
|
2月前
|
存储 SQL 安全
网络安全与信息安全:守护数字世界的坚盾在这个高度数字化的时代,网络安全和信息安全已经成为个人、企业乃至国家安全的重要组成部分。本文将深入探讨网络安全漏洞、加密技术以及安全意识的重要性,旨在为读者提供一个全面的网络安全知识框架。
随着互联网技术的飞速发展,网络安全问题日益凸显。从个人信息泄露到企业数据被盗,再到国家安全受到威胁,网络安全事件层出不穷。本文将从网络安全漏洞的定义与分类入手,探讨常见的网络攻击手段;随后深入解析加密技术的原理及其在保护信息安全中的作用;最后强调提升公众与企业的安全意识的重要性,并提出具体的建议。通过综合运用这些知识点,我们可以更好地构建起一道道坚固的防线,守护我们的数字世界。
|
2月前
|
编解码 分布式计算 网络协议
Netty高性能网络框架(一)
Netty高性能网络框架(一)
|
7天前
|
数据采集 前端开发 中间件
Python网络爬虫:Scrapy框架的实战应用与技巧分享
【10月更文挑战第26天】Python是一种强大的编程语言,在数据抓取和网络爬虫领域应用广泛。Scrapy作为高效灵活的爬虫框架,为开发者提供了强大的工具集。本文通过实战案例,详细解析Scrapy框架的应用与技巧,并附上示例代码。文章介绍了Scrapy的基本概念、创建项目、编写简单爬虫、高级特性和技巧等内容。
28 4
|
7天前
|
网络协议 物联网 API
Python网络编程:Twisted框架的异步IO处理与实战
【10月更文挑战第26天】Python 是一门功能强大且易于学习的编程语言,Twisted 框架以其事件驱动和异步IO处理能力,在网络编程领域独树一帜。本文深入探讨 Twisted 的异步IO机制,并通过实战示例展示其强大功能。示例包括创建简单HTTP服务器,展示如何高效处理大量并发连接。
24 1
|
13天前
|
网络协议 Shell 网络安全
解决两个 Android 模拟器之间无法网络通信的问题
让同一个 PC 上运行的两个 Android 模拟器之间能相互通信,出(qiong)差(ren)的智慧。
19 3
|
25天前
|
机器学习/深度学习 数据采集 算法
目标分类笔记(一): 利用包含多个网络多种训练策略的框架来完成多目标分类任务(从数据准备到训练测试部署的完整流程)
这篇博客文章介绍了如何使用包含多个网络和多种训练策略的框架来完成多目标分类任务,涵盖了从数据准备到训练、测试和部署的完整流程,并提供了相关代码和配置文件。
42 0
目标分类笔记(一): 利用包含多个网络多种训练策略的框架来完成多目标分类任务(从数据准备到训练测试部署的完整流程)