为什么片段无法显示空白屏幕?

时间:2019-11-13 20:16:28

标签: android android-fragments kotlin viewmodel android-mvvm

我正在开发新闻应用程序,并且已经在片段类中实现了ViewModel并在片段类中获取了数据,但是它正在加载进度条,仅不显示来自服务器的数据

在我的MainViewModel.kt下面

def inner_product(vec_1, vec_2):
    # Requirement 1
    if len(vec_1) != len(vec_2):
            return None
    # Requirement 2 
    elif vec_1 == [] or vec_2 == []: 
        return 0

    # Define a list to hold your values until you return it
    counter = [] 

    # range() takes a beginning integer (index in this case)
    # and an ending one. However, the range is exclusive of the last number
    # so even though len(vec_1) == 3, range will stop at 2

    for i in range(0, len(vec_2)):
        # Append the output to your counter array on each pass
        counter.append(vec_1[i]*vec_2[i])

    # Once the for loop is done, return the value    
    return counter

# These should all return True

assert inner_product([], []) == 0
assert inner_product([1,2,3], [1,2]) is None
assert inner_product([1,2,3], [3,4,5]) == [3,8,15]

在适配器类之下

class MainViewModel(newsRepository: Any?) : 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()
    }
}

在实现了ViewModel的TopHeadlinesFragment下方

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

    }

    @SuppressLint("NewApi")
    fun example() {
    }

    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)

    }
}

AppModules.kt下面

class TopHeadlinesFragment : Fragment() {

     private var viewModel: MainViewModel? = null
    private lateinit var topHeadlinesAdapter: TopHeadlinesAdapter


    //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
        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()
        })
    }
}

fragment_top_headlines.xml以下

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)
}

低于news_list.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ProgressBar
        android:id="@+id/pb"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

NewsRepository.kt以下

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:layout_marginBottom="16dp">

        <ImageView
            android:id="@+id/imageView"
            android:layout_width="100dp"
            android:layout_height="85dp"
            android:layout_marginStart="16dp"
            android:layout_marginLeft="16dp"
            android:contentDescription="bbc"
            tools:background="@color/colorPrimary" />

        <TextView
            android:id="@+id/articleTitle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginEnd="16dp"
            android:layout_toEndOf="@id/imageView"
            android:layout_toRightOf="@id/imageView"
            android:ellipsize="end"
            android:lines="3"
            android:maxLines="3"
            android:text="1\n2\n3\n" />

        <ImageView
            android:id="@+id/imageCategory"
            android:layout_width="32dp"
            android:layout_height="32dp"
            android:layout_below="@id/articleTitle"
            android:layout_marginStart="16dp"
            android:layout_marginLeft="16dp"
            android:layout_toEndOf="@id/imageView"
            android:layout_toRightOf="@id/imageView"
            android:src="@drawable/ic_espn"
            tools:background="@color/colorPrimary" />

        <TextView
            android:id="@+id/articleSourceName"
            android:layout_width="wrap_content"
            android:layout_height="32dp"
            android:layout_below="@id/articleTitle"
            android:layout_marginStart="16dp"
            android:layout_marginLeft="16dp"
            android:layout_toEndOf="@id/imageCategory"
            android:layout_toRightOf="@id/imageCategory"
            android:gravity="center|start"
            android:text="Onefootbal" />

        <TextView
            android:id="@+id/articleTime"
            android:layout_width="wrap_content"
            android:layout_height="32dp"
            android:layout_below="@id/articleTitle"
            android:layout_alignParentEnd="true"
            android:layout_alignParentRight="true"
            android:layout_marginStart="16dp"
            android:layout_marginEnd="16dp"
            android:layout_toEndOf="@id/articleSourceName"
            android:layout_toRightOf="@id/articleSourceName"
            android:gravity="center|start"
            android:text="- 1h"
            android:textColor="@android:color/darker_gray"
            tools:ignore="NotSibling" />
    </RelativeLayout>

</androidx.cardview.widget.CardView>

在SportInterface.kt下面,我正在获得终点

interface NewsRepository {
    // Suspend is used to await the result from Deferred
    suspend fun getNewsList(): UseCaseResult<Deferred<List<SportNewsResponse>>>
}

class NewsRepositoryImpl(private val sportNewsInterface: SportNewsInterface) : NewsRepository {
    override suspend fun getNewsList(): UseCaseResult<Deferred<List<SportNewsResponse>>> {
        /*
         We try to return a list of cats from the API
         Await the result from web service and then return it, catching any error from API
         */
        return try {
            val result = sportNewsInterface.getNews()
            UseCaseResult.Success(result) as UseCaseResult<Deferred<List<SportNewsResponse>>>
        } catch (ex: Exception) {
            UseCaseResult.Error(ex)
        }
    }
}

SportNewsResponse.kt以下

interface SportNewsInterface {

    @GET("v2/top-headlines?country=us&apiKey=da331087e3f3462bb534b3b0917cbee9")
    suspend fun getNews(): 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>>


}

Article.kt以下

data class SportNewsResponse(
    val articles: List<Article>,
    val status: String,
    val totalResults: Int
)

2 个答案:

答案 0 :(得分:0)

您的initViewModel看起来不太正常。尝试在下面使用:

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()
}

答案 1 :(得分:0)

首先 viewModel不应为null,在创建片段时,请使用lateinit并初始化viewModel。

第二,因为您使用的是Kotlin,所以不需要使用findViewById。 因此,您可以将所有初始视图移至onViewCreated而不是onCreateView

第三通过viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)初始化viewModel 或只是viewModel = MainViewModel()。这使您的viewModel可以工作。

毕竟,我不知道您的MainModel中的newsRepository的含义。您永远不会使用它,也许可以删除它。

class TopHeadlinesFragment : Fragment() {

   private lateinit var viewModel: MainViewModel
   private lateinit var topHeadlinesAdapter: TopHeadlinesAdapter

   override fun onCreateView(
       inflater: LayoutInflater,
       container: ViewGroup?,
       savedInstanceState: Bundle?
   ): View? {
       val view = inflater.inflate(
           R.layout.fragment_top_headlines
           , container, false
       )

       return view
   }

   override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

       topHeadlinesAdapter = TopHeadlinesAdapter(context)
       recyclerView.layoutManager = LinearLayoutManager(context)
       recyclerView.adapter = topHeadlinesAdapter

       initViewModel()

       viewModel.loadNews()
   }

   private fun initViewModel() {

       viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)

       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)
           })
       })
   }
}

希望这会有所帮助。