I have a fragment which has a RecyclerView
with items are in CardViews
. I have an adapter which will populate the RecyclerView
with data from newsapi.org
. what I need to achieve is when I click on a item(CardView) to load an activity with image, title and description. I'm quite new to kotlin and find I'm stuck in here and need help to follow up. Would be really helpful. I'll attach my adapter and fragment(which has the RecyclerView).
What bugs me is should i start the activity within onBindViewHolder -> ....cardView.setOnClickListener or else? and the confusing part is to set the image(which comes from url) into passing value to the details view.
Adapter Class
class ArticleAdapter(
private var articleList: ArrayList<Article>
) : RecyclerView.Adapter<ArticleViewHolder>() {
private val placeHolderImage = "https://picsum.photos/200/200/?blur"
private lateinit var viewGroupContext: Context
override fun onCreateViewHolder(viewGroup: ViewGroup, p1: Int): ArticleViewHolder {
viewGroupContext = viewGroup.context
val itemView: View =
LayoutInflater.from(viewGroup.context).inflate(R.layout.article_item, viewGroup, false)
return ArticleViewHolder(itemView)
}
override fun getItemCount(): Int {
return articleList.size
}
override fun onBindViewHolder(articleViewHolder: ArticleViewHolder, itemIndex: Int) {
val article: Article = articleList.get(itemIndex)
setPropertiesForArticleViewHolder(articleViewHolder, article)
articleViewHolder.cardView.setOnClickListener {
//do something
}
}
private fun setPropertiesForArticleViewHolder(
articleViewHolder: ArticleViewHolder,
article: Article
) {
checkForUrlToImage(article, articleViewHolder)
articleViewHolder.title.text = article?.title
articleViewHolder.description.text = article?.description
articleViewHolder.url.text = article?.url
}
private fun checkForUrlToImage(article: Article, articleViewHolder: ArticleViewHolder) {
if (article.urlToImage == null || article.urlToImage.isEmpty()) {
Picasso.get()
.load(placeHolderImage)
.centerCrop()
.fit()
.into(articleViewHolder.urlToImage)
} else {
Picasso.get()
.load(article.urlToImage)
.centerCrop()
.fit()
.into(articleViewHolder.urlToImage)
}
}
fun setArticles(articles: ArrayList<Article>) {
articleList = articles
notifyDataSetChanged()
}
}
//interface ItemClickListener{
// fun onItemClick(articleList: Article, position:Int)
//}
Fragment
class HomeFragment : Fragment(), SwipeRefreshLayout.OnRefreshListener {
// private lateinit var homeViewModel: HomeViewModel
private val ENDPOINT_URL by lazy { "https://newsapi.org/v2/" }
private lateinit var topHeadlinesEndpoint: TopHeadlinesEndpoint
private lateinit var newsApiConfig: String
private lateinit var articleAdapter: ArticleAdapter
private lateinit var articleList: ArrayList<Article>
private lateinit var userKeyWordInput: String
// RxJava related fields
private lateinit var topHeadlinesObservable: Observable<TopHeadlines>
private lateinit var compositeDisposable: CompositeDisposable
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_home, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
swipe_refresh.setOnRefreshListener {
queryTopHeadlines()
// refreshAction() //refresh the list
swipe_refresh.isRefreshing = false
}
//Network request
val retrofit: Retrofit = generateRetrofitBuilder()
topHeadlinesEndpoint = retrofit.create(TopHeadlinesEndpoint::class.java)
newsApiConfig = resources.getString(R.string.api_key)
swipe_refresh.setOnRefreshListener(this)
swipe_refresh.setColorSchemeResources(R.color.colorAccent)
articleList = ArrayList()
articleAdapter = ArticleAdapter(articleList)
// userKeyWordInput = ""
compositeDisposable = CompositeDisposable()
recycler_viewHome.setHasFixedSize(true)
recycler_viewHome.layoutManager = LinearLayoutManager(context)
recycler_viewHome.itemAnimator = DefaultItemAnimator()
recycler_viewHome.adapter = articleAdapter
}
override fun onStart() {
super.onStart()
queryTopHeadlines()
}
override fun onDestroy() {
super.onDestroy()
compositeDisposable.clear()
}
override fun onRefresh() {
queryTopHeadlines()
}
private fun queryTopHeadlines() {
swipe_refresh.isRefreshing = true
topHeadlinesObservable = topHeadlinesEndpoint.getTopHeadlines("us", newsApiConfig)
subscribeObservableOfArticle()
}
private fun subscribeObservableOfArticle() {
articleList.clear()
compositeDisposable.add(
topHeadlinesObservable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap {
Observable.fromIterable(it.articles)
}
.subscribeWith(createArticleObserver())
)
}
private fun createArticleObserver(): DisposableObserver<Article> {
return object : DisposableObserver<Article>() {
override fun onNext(article: Article) {
if (!articleList.contains(article)) {
articleList.add(article)
}
}
override fun onComplete() {
showArticlesOnRecyclerView()
}
override fun onError(e: Throwable) {
Log.e("createArticleObserver", "Article error: ${e.message}")
}
}
}
private fun showArticlesOnRecyclerView() {
if (articleList.size > 0) {
empty_text.visibility = View.GONE
retry_fetch_button.visibility = View.GONE
recycler_viewHome.visibility = View.VISIBLE
articleAdapter.setArticles(articleList)
} else {
recycler_viewHome.visibility = View.GONE
empty_text.visibility = View.VISIBLE
retry_fetch_button.visibility = View.VISIBLE
// retry_fetch_button.setOnClickListener { checkUserKeywordInput() }
}
swipe_refresh.isRefreshing = false
}
private fun generateRetrofitBuilder(): Retrofit {
return Retrofit.Builder()
.baseUrl(ENDPOINT_URL)
.addConverterFactory(GsonConverterFactory.create())
//Add RxJava2CallAdapterFactory as a Call adapter when building your Retrofit instance
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
}
}
First you need to pass the data from your cardView's onclick and then start activity, finally handle those data in your desired item details activity...
You'll need context/AppCompatActivity
while starting activity, so you can modify your Adapter's constructor to receive Context
:
class ArticleAdapter(
private val context: Context,
private var articleList: ArrayList<Article>
) : RecyclerView.Adapter<ArticleViewHolder>()
Use this constructor while initializing it from your fragment
:
articleAdapter = ArticleAdapter(activity, articleList) // activity => getActivity()
In your item click listener:
override fun onBindViewHolder(articleViewHolder: ArticleViewHolder, itemIndex: Int) {
val article: Article = articleList?.get(itemIndex)
setPropertiesForArticleViewHolder(articleViewHolder, article)
articleViewHolder.cardView.setOnClickListener {
val titleString = article.title
val descString = article.description
val urlString = article.url
val toPass = Bundle()
toPass.putString("url", urlString)
toPass.putString("title", titleString)
toPass.putString("desc", descString)
val intent =
Intent(context, YourActivity::class.java) //context we got from constructor
intent.putExtras(toPass)
context.startActivity(intent) // or we can use ContextCompat
}
}
Now handle this data and set views accordingly:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val bundle = intent.extras
val url = bundle?.get("url")
val title = bundle?.get("title")
val desc = bundle?.get("desc")
// now handle those...
titleTextView.text = title!!
// ...
}