android - 为什么数据不显示在片段中
问题描述
我正在开发一个新闻应用程序,我已经使用 ViewModel 和 Koin 对 Kotlin 协程进行了改造,但在我的 TopHeadlinesFragment 数据中没有显示。我不明白我在哪里犯错
在我实现网络的 appModules.kt 下方。
const val BASE_URL = "https://newsapi.org/"
val appModules = module {
// The Retrofit service using our custom HTTP client instance as a singleton
single {
createWebService<SportNewsInterface>(
okHttpClient = createHttpClient(),
factory = RxJava2CallAdapterFactory.create(),
baseUrl = BASE_URL
)
}
// Tells Koin how to create an instance of CatRepository
factory<NewsRepository> { (NewsRepositoryImpl(sportNewsInterface = get())) }
// Specific viewModel pattern to tell Koin how to build MainViewModel
viewModel { MainViewModel (newsRepository = get ()) }
}
/* Returns a custom OkHttpClient instance with interceptor. Used for building Retrofit service */
fun createHttpClient(): OkHttpClient {
val client = OkHttpClient.Builder()
client.readTimeout(5 * 60, TimeUnit.SECONDS)
return client.addInterceptor {
val original = it.request()
val requestBuilder = original.newBuilder()
requestBuilder.header("Content-Type", "application/json")
val request = requestBuilder.method(original.method, original.body).build()
return@addInterceptor it.proceed(request)
}.build()
}
/* function to build our Retrofit service */
inline fun <reified T> createWebService(
okHttpClient: OkHttpClient,
factory: CallAdapter.Factory, baseUrl: String
): T {
val retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().setLenient().create()))
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.addCallAdapterFactory(factory)
.client(okHttpClient)
.build()
return retrofit.create(T::class.java)
}
在 NewsRepository.kt 下面
interface NewsRepository {
// Suspend is used to await the result from Deferred
suspend fun getNewsList(): UseCaseResult<List<Article>>
}
class NewsRepositoryImpl(private val sportNewsInterface: SportNewsInterface) : NewsRepository {
override suspend fun getNewsList(): UseCaseResult<List<Article>> {
return try {
val result = sportNewsInterface.getNews()
UseCaseResult.Success(result) as UseCaseResult<List<Article>>
} catch (ex: Exception) {
UseCaseResult.Error(ex)
}
}
}
在我的新闻界面下方
接口 SportNewsInterface {
@GET("v2/top-headlines?country=us&apiKey=da331087e3f3462bb534b3b0917cbee9")
suspend fun getNews(): Deferred<List<SportNewsResponse>>
@GET("/v2/top-headlines?sources=espn&apiKey=da331087e3f3462bb534b3b0917cbee9")
fun getEspn(): Deferred<List<SportNewsResponse>>
@GET("/v2/top-headlines?sources=football-italia&apiKey=da331087e3f3462bb534b3b0917cbee9")
fun getFootballItalia(): Deferred<List<SportNewsResponse>>
@GET("/v2/top-headlines?sources=bbc-sport&apiKey=da331087e3f3462bb534b3b0917cbee9")
fun getBBCSport(): Deferred<List<SportNewsResponse>>
}
在我的 SportNewsResponse.kt 下方
data class SportNewsResponse(
val articles: List<Article>,
val status: String,
val totalResults: Int
)
在 Article.kt 模型类下面
data class Source(
val id: Any?,
val name: String
)
在未显示数据的 TopHeadlinesFragment 下方
class TopHeadlinesFragment : Fragment() {
private var viewModel: MainViewModel? = null
private lateinit var topHeadlinesAdapter: TopHeadlinesAdapter
private val newsRepository: NewsRepository by inject()
//3
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(
R.layout.fragment_top_headlines
, container, false
)
val recyclerView = view.findViewById(R.id.recyclerView) as RecyclerView
val pb = view.findViewById(R.id.pb) as ProgressBar
topHeadlinesAdapter = TopHeadlinesAdapter(recyclerView.context)
recyclerView.layoutManager = LinearLayoutManager(context)
recyclerView.adapter = topHeadlinesAdapter
val param = newsRepository
val factory = MainViewModelFactory(param)
viewModel = ViewModelProviders.of(this, factory).get(MainViewModel::class.java)
initViewModel()
return view
}
private fun initViewModel() {
viewModel?.sportList?.observe(this, Observer { newList ->
topHeadlinesAdapter.updateData(newList)
})
viewModel?.showLoading?.observe(this, Observer { showLoading ->
pb.visibility = if (showLoading) View.VISIBLE else View.GONE
})
viewModel?.showError?.observe(this, Observer { showError ->
(showError)
})
viewModel?.loadNews()
}
}
在 TopHeadlinesAdapter.kt 下方
class TopHeadlinesAdapter(val context: Context) :
RecyclerView.Adapter<TopHeadlinesAdapter.MyViewHolder>() {
private var articleList: List<Article> by Delegates.observable(emptyList()) { _, _, _ ->
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.news_list, parent, false)
return MyViewHolder(view)
}
override fun getItemCount(): Int {
return articleList.size
}
@SuppressLint("NewApi")
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.articleTitle.text = articleList.get(position).title
holder.articleSourceName.text = articleList.get(position).source.name
Picasso.get().load(articleList.get(position).urlToImage).into(holder.image)
val input = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX")
val output = SimpleDateFormat("dd/MM/yyyy")
var d = Date()
try {
d = input.parse(articleList[5].publishedAt)
} catch (e: ParseException) {
try {
val fallback = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
fallback.timeZone = TimeZone.getTimeZone("UTC")
d = fallback.parse(articleList[5].publishedAt)
} catch (e2: ParseException) {
// TODO handle error
val formatted = output.format(d)
val timelinePoint = LocalDateTime.parse(formatted)
val now = LocalDateTime.now()
var elapsedTime = Duration.between(timelinePoint, now)
println(timelinePoint)
println(now)
elapsedTime.toMinutes()
holder.articleTime.text = "${elapsedTime.toMinutes()}"
}
}
}
fun updateData(newList: List<Article>) {
articleList = newList
Log.e("articleListSize",articleList?.size.toString())
}
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView!!) {
val image: ImageView = itemView!!.findViewById(R.id.imageView)
val articleTitle: TextView = itemView!!.findViewById(R.id.articleTitle)
val articleSourceName: TextView = itemView!!.findViewById(R.id.articleSourceName)
val imageCategory: ImageView = itemView!!.findViewById(R.id.imageCategory)
val articleTime: TextView = itemView!!.findViewById(R.id.articleTime)
}
}
在我来自服务器的 json 响应下方
{
"status": "ok",
"totalResults": 38,
"articles": [
{
"source": {
"id": "cnbc",
"name": "CNBC"
},
"author": "Holly Ellyatt",
"title": "Russia is now not the only pressing issue that NATO has to deal with - CNBC",
"description": "Heads of state and government are meeting in the U.K. this week for the 70th anniversary of the military alliance NATO.",
"url": "https://www.cnbc.com/2019/12/02/nato-summit-alliance-has-more-pressing-issues-than-russia-now.html",
"urlToImage": "https://image.cnbcfm.com/api/v1/image/106272467-1575218599700gettyimages-997112494.jpeg?v=1575218712",
"publishedAt": "2019-12-02T07:39:00Z",
"content": "US president Donald Trump is seen during his press conference at the 2018 NATO Summit in Brussels, Belgium on July 12, 2018.\r\nAs heads of state and government meet in the U.K. this week for the 70th anniversary of the military alliance NATO, discussions are l… [+8623 chars]"
},
{
"source": {
"id": null,
"name": "Chron.com"
},
"author": "Aaron Wilson",
"title": "Bill O'Brien gets game ball from Deshaun Watson after Texans' win over Patriots - Chron",
"description": "In an emotional moment, Texans coach Bill O'Brien was presented with the game ball by quarterback Deshaun Watson following a pivotal win over the New England Patriots.",
"url": "https://www.chron.com/sports/texans/article/Bill-O-Brien-Deshaun-Watson-Texans-Patriots-14874678.php",
"urlToImage": "https://s.hdnux.com/photos/01/07/23/50/18692664/3/rawImage.jpg",
"publishedAt": "2019-12-02T06:16:00Z",
"content": "<ul><li>Houston Texans head coach Bill O'Brien on the sidelines during the fourth quarter of an NFL game against the New England Patriots at NRG Stadium Sunday, Dec. 1, 2019, in Houston.\r\nHouston Texans head coach Bill O'Brien on the sidelines during the four… [+1583 chars]"
}
]
}
在我的 MainViewModel.kt 下面
class MainViewModel(newsRepository: NewsRepository) : ViewModel(), CoroutineScope {
// Coroutine's background job
val job = Job()
val sportNewsInterface: SportNewsInterface? = null
// Define default thread for Coroutine as Main and add job
override val coroutineContext: CoroutineContext = Dispatchers.Main + job
val showLoading = MutableLiveData<Boolean>()
val sportList = MutableLiveData <List<Article>>()
val showError = SingleLiveEvent<String>()
fun loadNews(
) {
// Show progressBar during the operation on the MAIN (default) thread
showLoading.value = true
// launch the Coroutine
launch {
// Switching from MAIN to IO thread for API operation
// Update our data list with the new one from API
val result = withContext(Dispatchers.IO) {
sportNewsInterface?.getNews()
}
// Hide progressBar once the operation is done on the MAIN (default) thread
showLoading.value = false
when (result) {
is UseCaseResult.Success<*> -> {
sportList.value = result.data as List<Article>
}
is Error -> showError.value = result.message
}
}
}
override fun onCleared() {
super.onCleared()
// Clear our job when the linked activity is destroyed to avoid memory leaks
job.cancel()
}
}
解决方案
根据我在您的代码中看到的内容MainViewModel
,您正在尝试从变量中获取结果sportNewsInterface
:
val result = withContext(Dispatchers.IO) {
sportNewsInterface?.getNews()
}
这个变量被初始化null
并且永远不会被初始化。我想这是一个拼写错误,你应该newsRepository.getNewsList()
改用它。
在的构造函数中添加val
before ,因此变量将成为该类的成员并使用它而不是在方法中。newsRepository
MainViewModel
sportNewsInterface
loadNews()
更新
作者更新了他的代码并使用 gist 文件发送更新版本:https ://gist.github.com/kyodgorbek/9f0e1eb8696e4b88a354e8a372a8e690
恢复您的代码后,您必须添加一些更改:
- 班级
TopHeadlinesFragment
首先,那里的 viewmodel 变量没有初始化。由于您使用 Koin 提供它,因此使用以下代码更改第 3 行:
private var viewModel by viewModel<MainViewModel>()
- 文件
NewsRepository.kt
,类NewsRepositoryImpl
。
这就是循环依赖的地方。如果您查看 的构造函数NewsRepositoryImpl
,您会注意到该类接受类型为 的参数NewsRepository
。这实际上产生了循环依赖,因为这种类型已经存在并且在appModules
Koin 模块中提供。话虽如此,您应该将该参数替换为 的实例SportNewsInterface
,因此该类NewsRepositoryImpl
将如下所示:
@Suppress("UNCHECKED_CAST")
class NewsRepositoryImpl(private val sportsNewsApi: SportNewsInterface) : NewsRepository {
override suspend fun getNewsList(): UseCaseResult<List<Article>> {
return try {
val result = sportsNewsApi.getNews()
UseCaseResult.Success(result) as UseCaseResult<List<Article>>
} catch (ex: Exception) {
UseCaseResult.Error(ex)
}
}
}
- 班级
MainViewModel
在这里您应该删除第 5 行val newsRepository: NewsRepository? = null
,因为您已经在类的构造函数中将该依赖项作为参数。除此之外,为了将参数放入字段中,请在val
此之前添加。结果,您的代码将如下所示:
@Suppress("UNCHECKED_CAST")
class MainViewModel(val newsRepository: NewsRepository) : ViewModel(), CoroutineScope {
// Coroutine's background job
val job = Job()
// Define default thread for Coroutine as Main and add job
override val coroutineContext: CoroutineContext = Dispatchers.Main + job
val showLoading = MutableLiveData<Boolean>()
val sportList = MutableLiveData <List<Article>>()
val showError = SingleLiveEvent<String>()
fun loadNews() {
// Show progressBar during the operation on the MAIN (default) thread
showLoading.value = true
// launch the Coroutine
launch {
// Switching from MAIN to IO thread for API operation
// Update our data list with the new one from API
val result = withContext(Dispatchers.IO) {
newsRepository?.getNewsList()
}
// Hide progressBar once the operation is done on the MAIN (default) thread
showLoading.value = false
when (result) {
is UseCaseResult.Success<*> -> {
sportList.value = result.data as List<Article>
}
is Error -> showError.value = result.message
}
}
}
override fun onCleared() {
super.onCleared()
// Clear our job when the linked activity is destroyed to avoid memory leaks
job.cancel()
}
}
推荐阅读
- python - 具有非均匀矩阵网格的 Matplotlib 等高线图
- python - 如何在 Python 中对 mixin 类进行参数化?
- excel - 有没有办法将列中的一组值分配给一个变量?
- firebase - 将 react-native Picker 绑定到从 Firebase 返回的数据
- javascript - 返回指令在javascript中停止我的for循环
- mongodb - 你如何将 Mongoose 验证错误传递给 JSON for Postman
- javascript - 如何在 Firefox 浏览器上运行 Few Specs [it blocks] 并在量角器的 Chrome 浏览器上运行几个 specs [it blocks]
- r - 如何每天运行回归并将系数保存在新数据集中?
- swift - SwiftUI TextField 简单示例不起作用
- typescript - 声明数组,但它显示一个 obj