-1

I am developing a news app and I have implemented ViewModel in fragment class and getting data in fragment class but it is loading progress bar only not showing data which coming from server

below My MainViewModel.kt

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

below Adapter class

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)

    }
}

below TopHeadlinesFragment where I have implemented ViewModel

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

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

below fragment_top_headlines.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>

below news_list.xml

<?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>

below NewsRepository.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)
        }
    }
}

below SportInterface.kt where I am getting ending points

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


}

below SportNewsResponse.kt

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

below Article.kt

@Entity(tableName = "news_table")
data class Article(@ColumnInfo(name = "author")val author: String,
                   val content: String,
                   val description: String,
                   val publishedAt: String,
                   val source: Source,
                   val title: String,
                   val url: String,
                   val urlToImage: String
)
Edgar
  • 860
  • 1
  • 17
  • 38

2 Answers2

0

Your initViewModel doesn't looks ok. Try using below:

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()
}
Md. Asaduzzaman
  • 14,963
  • 2
  • 34
  • 46
  • Asaduzzamman java.lang.NoClassDefFoundError: Failed resolution of: Lorg/koin/core/parameter/ParameterListKt; at yodgorbek.komilov.musobaqayangiliklari.ui.TopHeadlinesFragment.(TopHeadlinesFragment.kt:90) at yodgorbek.komilov.musobaqayangiliklari.MainActivity.onCreate(MainActivity.kt:24) – Edgar Nov 13 '19 at 22:36
0

First viewModel should not be null, use lateinit and init viewModel while fragment is created.

Second you dont need to use findViewById because you are using Kotlin. So you can move all your initial view into onViewCreated instead of onCreateView

Third initialize viewModel by viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java) or just viewModel = MainViewModel(). That make your viewModel works.

After all, I dont know the newsRepository meaning in your MainModel. You never use it, maybe you can delete it.

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

Hope this helps.

GHH
  • 1,713
  • 1
  • 8
  • 21