Observing MediatorLiveData Issue - kotlin

I have the following LiveData variables in my ViewModel (simplified example):
val currentUser : LiveData<UserObject>
val allSites : LiveData<ArrayList<SiteObject>>
val filterSitesForUser : LiveData<Boolean>
val orderSitesByField : LiveData<String>
val orderSitesDirection : LiveData<Query.Direction>
val searchFilterSitesText : LiveData<String>
I'm trying to use MediatorLiveData to have one 'stream' of data connecting to my RecyclerView.
I therefore also have the following code in the ViewModel, which is observed in the Fragment:
fun sitesList() : LiveData<ArrayList<SiteObject>> {
val result = MediatorLiveData<ArrayList<SiteObject>>()
result.addSource(currentUser) {
result.value = combineSitesData(currentUser, allSites, filterSitesForUser, orderSitesByField, orderSitesDirection, searchFilterSitesText)
}
result.addSource(allSites) {
result.value = combineSitesData(currentUser, allSites, filterSitesForUser, orderSitesByField, orderSitesDirection, searchFilterSitesText)
}
result.addSource(filterSitesForUser) {
result.value = combineSitesData(currentUser, allSites, filterSitesForUser, orderSitesByField, orderSitesDirection, searchFilterSitesText)
}
result.addSource(orderSitesByField) {
result.value = combineSitesData(currentUser, allSites, filterSitesForUser, orderSitesByField, orderSitesDirection, searchFilterSitesText)
}
result.addSource(orderSitesDirection) {
result.value = combineSitesData(currentUser, allSites, filterSitesForUser, orderSitesByField, orderSitesDirection, searchFilterSitesText)
}
result.addSource(searchFilterSitesText) {
result.value = combineSitesData(currentUser, allSites, filterSitesForUser, orderSitesByField, orderSitesDirection, searchFilterSitesText)
}
return result
}
..along with this, also in the ViewModel:
private fun combineSitesData (
currentUser: LiveData<UserObject>,
allSites: LiveData<ArrayList<SiteObject>>,
filterSitesForUser: LiveData<Boolean>,
orderSitesByField: LiveData<String>,
orderSitesDirection: LiveData<Query.Direction>,
searchFilterSitesText: LiveData<String>
) : ArrayList<SiteObject> {
var sitesList = ArrayList<SiteObject>()
val userId = currentUser.value?.userID
if (userId == null || allSites.value == null) {
Log.d(TAG, "combineSitesData() - currentUser or allSites value null")
return sitesList
}
when (filterSitesForUser.value) {
true -> sitesList.addAll(allSites.value!!.filter { site -> site.users.contains(userId) })
false -> sitesList.addAll(allSites.value!!)
}
if (orderSitesDirection.value == Query.Direction.ASCENDING){
when (orderSitesByField.value) {
DATE_CREATED -> sitesList.sortBy { it.dateCreatedTimestamp }
DATE_EDITED -> sitesList.sortBy { it.dateEditedTimestamp }
SITE_TASKS -> sitesList.sortBy { it.siteTask }
SITE_RATING -> sitesList.sortBy { it.siteRating }
else -> sitesList.sortBy {it.siteReference}
}
}
if (orderSitesDirection.value == Query.Direction.DESCENDING){
when (orderSitesByField.value) {
DATE_CREATED -> sitesList.sortByDescending { it.dateCreatedTimestamp }
DATE_EDITED -> sitesList.sortByDescending { it.dateEditedTimestamp }
SITE_TASKS -> sitesList.sortByDescending { it.siteTask }
SITE_RATING -> sitesList.sortByDescending { it.siteRating }
else -> sitesList.sortByDescending {it.siteReference}
}
}
if (!searchFilterSitesText.value.isNullOrEmpty()) {
var filteredList = ArrayList<SiteObject>()
var filterPattern = searchFilterSitesText.value.toString().toLowerCase().trim()
for (site in sitesList) {
if(site.siteReference.toLowerCase().contains(filterPattern) || site.siteAddress.toLowerCase().contains(filterPattern)) {
filteredList.add(site)
}
}
Log.d(TAG, "combineSitesData() - returned filteredList (size = ${filteredList.size})")
return filteredList
}
Log.d(TAG, "combineSitesData() - returned sitesList (size = ${sitesList.size})")
return sitesList
}
This is the code in the Fragment which observes the list of Sites:
// Observe ViewModel SitesList
businessViewModel.sitesList().observe(viewLifecycleOwner, Observer { sitesList ->
if (sitesList != null) {
businessViewModel.currentUser.value?.userID?.let { sitesAdapter.setList(it, sitesList) }
sitesAdapter.notifyDataSetChanged()
Log.d (TAG, "setupViewModelObservers(): businessViewModel.sitesList().size = ${sitesList.size}" )
}
})
It seems to be working apart from the fact that when it is first observes, it fires off six times, which therefore updates my RecylcerView six times - when this is a large list, I see this being a bit of an issue!
After it initialises, it only updates once when any of the LiveData's change, so that's ok.
I'm not sure how to prevent this from happening, without holding a list in the Fragment or ViewModel to compare observed list to, which I am trying to avoid (as trying to follow MVVM architecture)..
EDIT: for clarity, I have added my RecyclerViewAdapter to show what the function setList does:
class SitesRecyclerViewAdapter(
private var currentUserId: String,
private val onItemClickedListener: (SiteObject) -> Unit,
private val onItemLongClickedListener: (SiteObject) -> Boolean
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var sitesList = ArrayList<SiteObject>()
class SiteViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val listItemBinding = SitesListItemBinding.bind(itemView)
fun bind(
userId: String,
site: SiteObject,
clickListener: (SiteObject) -> Unit,
longClickListener: (SiteObject) -> Boolean
) {
// Set text fields
listItemBinding.sitesItemTitleText.text = site.siteReference
// listItemBinding.sitesItemProjectsText.text = site.recentProjectsText TODO: Create way of showing recent projects with arrays
//Reset Icons visibility
listItemBinding.sitesItemTaskImageView.visibility = View.INVISIBLE
listItemBinding.sitesItemRating1ImageView.visibility = View.INVISIBLE
listItemBinding.sitesItemRating2ImageView.visibility = View.INVISIBLE
listItemBinding.sitesItemRating3ImageView.visibility = View.INVISIBLE
listItemBinding.sitesItemFavouriteImageView.visibility = View.GONE
//Set sitePriority Icon visibility
if (site.siteTask) listItemBinding.sitesItemTaskImageView.visibility = View.VISIBLE
//Set siteRating Icon visibility
when(site.siteRating){
1 -> listItemBinding.sitesItemRating1ImageView.visibility = View.VISIBLE
2 -> listItemBinding.sitesItemRating2ImageView.visibility = View.VISIBLE
3 -> listItemBinding.sitesItemRating3ImageView.visibility = View.VISIBLE
}
//Set siteFavourite Icon visibility
if (site.users.contains(userId)) listItemBinding.sitesItemFavouriteImageView.visibility = View.VISIBLE
// Set Listeners
listItemBinding.sitesItemCardview.setOnClickListener { clickListener(site) }
listItemBinding.sitesItemCardview.setOnLongClickListener { longClickListener(site) }
// Not set map listener?
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
// LayoutInflater: takes ID from layout defined in XML.
// Instantiates the layout XML into corresponding View objects.
// Use context from main app -> also supplies theme layout values!
val inflater = LayoutInflater.from(parent.context)
// Inflate XML. Last parameter: don't immediately attach new view to the parent view group
val view = inflater.inflate(R.layout.sites_list_item, parent, false)
return SiteViewHolder(view)
}
override fun getItemCount(): Int = sitesList.size
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
// Populate ViewHolder with data that corresponds to the position in the list
// which we are told to load
(holder as SiteViewHolder).bind(
currentUserId,
sitesList[position],
onItemClickedListener,
onItemLongClickedListener
)
}
fun setList(userID: String, observedSites: ArrayList<SiteObject>) {
currentUserId = userID
sitesList = observedSites
notifyDataSetChanged()
Log.d(TAG, "setList(), observedSites size = ${observedSites.size}")
}
}

You can use postValue instead of setValue
If you called this method multiple times before a main thread executed a posted task, only
the last value would be dispatched.

Related

Loading state adapter is always loading

My task is to load recyclerView and show pagination which I both implemented.
I also implemented LoadingState for adapter.
ApiCall:
#GET("top-headlines?sources=bbc-news,techcrunch&apiKey=${BuildConfig.API_KEY}")
suspend fun getTopHeadlinesArticles(
#Query("page") page:Int = 1,
#Query("q") query: String,
) : Response<ArticleListResponse>
I wont show paging because it is working so I will jump to repository:
fun getSearchResult(query: String) =
Pager(
config = PagingConfig(
pageSize = 1,
maxSize = 20,
enablePlaceholders = false
),
pagingSourceFactory = { ArticlePaging(newsService, query)}
).flow
ViewModel:
#OptIn(ExperimentalCoroutinesApi::class)
val news = _currentQuery.flatMapLatest { query ->
articleRepository.getSearchResult(query).cachedIn(viewModelScope)
}
Fragment:
binding.recyclerViewTop.layoutManager = LinearLayoutManager(context)
binding.recyclerViewTop.adapter = adapter.withLoadStateHeaderAndFooter(
header = ArticleLoadStateAdapter { adapter.retry() },
footer = ArticleLoadStateAdapter { adapter.retry() }
)
lifecycleScope.launchWhenCreated {
viewModel.news.collect { articles ->
adapter.submitData(articles)
}
}
And LoadStateViewHolder in LoadStateAdapter:
init {
binding.buttonRetry.setOnClickListener {
retry.invoke()
}
}
fun bind(loadState: LoadState) {
binding.apply {
progressBar.isVisible = loadState is LoadState.Loading
buttonRetry.isVisible = loadState !is LoadState.Loading
textViewError.isVisible = loadState !is LoadState.Loading
}
}
I already predefined DEFAULT_QUERY so I only get 1 Article.
Problem is that progressBar loading is always visible and I have no more articles to show.
Edit: when i have at least 10 items to show this works fine
Edit 2nd: Add ArticlePagingSource if that can help
override fun getRefreshKey(state: PagingState<Int, ArticleResponse>): Int? {
return state.anchorPosition?.let {
val anchorPage = state.closestPageToPosition(it)
anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
}
}
try {
val currentPageList = params.key ?: 1
response = if(query != ""){
newsService.getTopHeadlinesArticles(currentPageList, query)
} else{
newsService.getTopHeadlinesArticles(currentPageList)
}
val responseList = mutableListOf<ArticleResponse>()
val data = response.body()?.articleResponses ?: emptyList()
responseList.addAll(data)
val prevKey = if (currentPageList == 1) null else currentPageList - 1
return LoadResult.Page(
responseList,
prevKey,
currentPageList.plus(1)
)

Observing live data from an API is not updating ui when data changes

I am trying to develop a football app demo. Data comes from an API from the api
It loads data as expected when app started, but when score of match changes, ui is not updating for scores by itself. I am using DiffUtil getChangePayload() to detect changes in score and status fields of Match objects which comes from the response. But it is not triggering when live match data changes. What am i missing?
P.S. I put layout in SwipeRefreshLayout and when i refresh, it gets scores and update the ui. But i want to see the match status and scores updating by itself.
Here is my code:
class MatchesViewModel(
app: Application,
private val repository: MatchesRepository
): AndroidViewModel(app) {
val matchesToday: MutableLiveData<List<Matche>> = MutableLiveData()
init {
getMatchesToday()
}
fun getMatchesToday() = viewModelScope.launch {
safeMatchesToday()
}
private suspend fun safeMatchesToday() {
if (Constants.checkConnection(this)) {
val response = repository.getMatchesToday()
if (response.isSuccessful) {
response.body()?.let {
matchesToday.postValue(it.matches)
}
}
}
}
}
class MatchesTodayFragment : Fragment() {
private var _binding: FragmentMatchesTodayBinding? =null
private val binding get() = _binding!!
private lateinit var mMatchesAdapter: MatchesAdapter
private val viewModel: MatchesViewModel by viewModels {
MatchesViewModelFactory(requireActivity().application, (requireActivity().application as MatchesApplication).repository)
}
#RequiresApi(Build.VERSION_CODES.N)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupRecyclerView()
viewModel.matchesToday.observe(viewLifecycleOwner) { matches ->
mMatchesAdapter.differ.submitList(matches)
}
binding.srlMatchesToday.setOnRefreshListener {
viewModel.getMatchesToday()
binding.srlMatchesToday.isRefreshing = false
}
}
}
class MatchesAdapter(val fragment: Fragment): RecyclerView.Adapter<MatchesAdapter.ViewHolder>() {
private val differCallback = object: DiffUtil.ItemCallback<Matche>() {
override fun areItemsTheSame(oldItem: Matche, newItem: Matche): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Matche, newItem: Matche): Boolean {
return oldItem.status == newItem.status &&
oldItem.score.fullTime.home == newItem.score.fullTime.home &&
oldItem.score.fullTime.away == newItem.score.fullTime.away &&
oldItem == newItem
}
override fun getChangePayload(oldItem: Matche, newItem: Matche): Any? {
val bundle: Bundle = bundleOf()
if (oldItem.status != newItem.status) {
bundle.apply {
putString(Constants.MATCH_STATUS, newItem.status)
}
}
if (oldItem.score.fullTime.home != newItem.score.fullTime.home) {
bundle.apply {
putInt(Constants.HOME_SCORE, newItem.score.fullTime.home)
}
}
if (oldItem.score.fullTime.away != newItem.score.fullTime.away) {
bundle.apply {
putInt(Constants.AWAY_SCORE, newItem.score.fullTime.away)
}
}
if (bundle.size() == 0) {
return null
}
return bundle
}
}
val differ = AsyncListDiffer(this, differCallback)
#SuppressLint("UseCompatLoadingForDrawables")
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val match = differ.currentList[position]
holder.apply {
Glide.with(fragment)
.load(match.homeTeam.crest)
.placeholder(fragment.resources.getDrawable(R.drawable.ic_ball))
.into(ivHomeTeamImage)
Glide.with(fragment)
.load(match.awayTeam.crest)
.placeholder(fragment.resources.getDrawable(R.drawable.ic_ball))
.into(ivAwayTeamImage)
tvHomeTeamName.text = match.homeTeam.name
tvAwayTeamName.text = match.awayTeam.name
when (match.status) {
Constants.TIMED -> {
tvMatchTime.text = Constants.toTimeForTR(match.utcDate)
tvHomeTeamScore.text = "-"
tvAwayTeamScore.text = "-"
}
Constants.PAUSED -> {
tvMatchTime.text = Constants.FIRST_HALF
tvHomeTeamScore.text = match.score.fullTime.home.toString()
tvAwayTeamScore.text = match.score.fullTime.away.toString()
}
Constants.FINISHED -> {
tvMatchTime.text = Constants.FINISHED
tvHomeTeamScore.text = match.score.fullTime.home.toString()
tvAwayTeamScore.text = match.score.fullTime.away.toString()
}
else -> {
tvMatchTime.text = Constants.IN_PLAY
tvHomeTeamScore.text = match.score.fullTime.home.toString()
tvAwayTeamScore.text = match.score.fullTime.away.toString()
}
}
}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
if (payloads.isNotEmpty()) {
val item = payloads[0] as Bundle
val status = item.getString(Constants.MATCH_STATUS)
val homeScore = item.getInt(Constants.HOME_SCORE)
val awayScore = item.getInt(Constants.AWAY_SCORE)
holder.apply {
tvMatchTime.text = status
tvHomeTeamScore.text = homeScore.toString()
tvAwayTeamScore.text = awayScore.toString()
Log.e("fuck", status.toString())
}
}
super.onBindViewHolder(holder, position, payloads)
}
override fun getItemCount(): Int {
return differ.currentList.size
}
}
LiveData only pushes new values if you command it to. Since you want to do it repeatedly, you need to create a loop. This is very easy to do using the liveData coroutine builder.
class MatchesViewModel(
app: Application,
private val repository: MatchesRepository
): AndroidViewModel(app) {
val matchesToday = liveData {
while (true) {
if (Constants.checkConnection(this)) {
val response = repository.getMatchesToday()
if (response.isSuccessful) {
response.body()?.let {
emit(it.matches)
}
}
}
delay(5000) // however many ms you want between fetches
}
}
}
If this is a Retrofit response, I think checking isSuccessful is redundant because body() will be non-null if and only if isSuccessful is true. So it could be simplified a bit from what you have:
class MatchesViewModel(
app: Application,
private val repository: MatchesRepository
): AndroidViewModel(app) {
val matchesToday = liveData {
while (true) {
if (Constants.checkConnection(this)) {
repository.getMatchesToday()?.body()?.matches?.let(::emit)
}
delay(5000) // however many ms you want between fetches
}
}
}

RecyclerView and OnItemClick: No value passed for parameter 'click' in Fragment

I'm trying to implement an OnClick listener for specific items in a recyclerview. I've done it before with activities, but now I want to do this in a fragment and I've run into a problem. I'm getting the error No value passed for parameter 'click'. This is the line of code that gives that error:
recyclerAdapterAbstract = abstractList.let {AbstractAdapter(requireActivity(),it)}
This the only error Android Studio is currently showing me. The logcat doesn't show any error. Only the Build Output shows what's wrong and it's exactly what I said earlier
Fragment Class
#Suppress("UNREACHABLE_CODE")
class AbstractWallpapers: Fragment(), PurchasesUpdatedListener, AbstractAdapter.OnItemClickListenerAbstract {
private lateinit var subscribeAbstract: Button
private var billingClient: BillingClient? = null
lateinit var recyclerView: RecyclerView
lateinit var abstractList: ArrayList<Abstract>
private var recyclerAdapterAbstract: AbstractAdapter? = null
private var myRef3: DatabaseReference? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_abstract_wallpaper, container, false)
recyclerView = requireActivity().findViewById(R.id.abstract_recyclerView)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
recyclerView = view.findViewById(R.id.abstract_recyclerView)
val layoutManager = GridLayoutManager(requireContext(), 2)
recyclerView.layoutManager = layoutManager
recyclerView.setHasFixedSize(true)
myRef3 = FirebaseDatabase.getInstance().reference
abstractList = ArrayList()
ClearAll()
GetDataFromFirebase()
subscribeAbstract = view.findViewById(R.id.abstract_subscribe_btn)
subscribeAbstract.setOnClickListener {
subscribeAbstract()
}
//Establish connection to billing client
//check subscription status from google play store cache
//to check if item is already Subscribed or subscription is not renewed and cancelled
billingClient = BillingClient.newBuilder(requireActivity()).enablePendingPurchases().setListener(this).build()
billingClient!!.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
val queryPurchase = billingClient!!.queryPurchases(BillingClient.SkuType.SUBS)
val queryPurchases = queryPurchase.purchasesList
if (queryPurchases != null && queryPurchases.size > 0) {
handlePurchases(queryPurchases)
} else {
saveSubscribeValueToPref(false)
}
}
}
override fun onBillingServiceDisconnected() {
Toast.makeText(requireActivity(), "Service Disconnected", Toast.LENGTH_SHORT).show()
}
})
//item subscribed
if (subscribeValueFromPref) {
subscribeAbstract.visibility = View.GONE
} else {
subscribeAbstract.visibility = View.VISIBLE
}
}
// Code relating to my Firebase storage
#SuppressLint("NotifyDataSetChanged")
private fun GetDataFromFirebase() {
val query: Query = myRef3!!.child("Abstract")
query.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
for (dataSnapshot: DataSnapshot in snapshot.children) {
val abstract = Abstract()
abstract.abstract = dataSnapshot.child("abstract").value.toString()
abstractList.add(abstract)}
recyclerAdapterAbstract = abstractList.let {AbstractAdapter(requireActivity(),it)}
recyclerView.adapter = recyclerAdapterAbstract
recyclerAdapterAbstract!!.notifyDataSetChanged()
}
override fun onCancelled(error: DatabaseError) {}
})
if (recyclerAdapterAbstract != null) recyclerAdapterAbstract!!.notifyDataSetChanged()
}
private fun ClearAll() {
abstractList.clear()
abstractList = ArrayList()
}
private val preferenceObject: SharedPreferences
get() = requireActivity().getSharedPreferences(PREF_FILE, 0)
private val preferenceEditObject: SharedPreferences.Editor
get() {
val pref = requireActivity().getSharedPreferences(PREF_FILE, 0)
return pref.edit()
}
private val subscribeValueFromPref: Boolean
get() = preferenceObject.getBoolean(SUBSCRIBE_KEY, false)
private fun saveSubscribeValueToPref(value: Boolean) {
preferenceEditObject.putBoolean(SUBSCRIBE_KEY, value).commit()
}
//initiate purchase on button click
fun subscribeAbstract() {
//check if service is already connected
if (billingClient!!.isReady) {
initiatePurchase()
} else {
billingClient = BillingClient.newBuilder(requireActivity()).enablePendingPurchases().setListener(this).build()
billingClient!!.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
initiatePurchase()
} else {
Toast.makeText(requireActivity(), "Error " + billingResult.debugMessage, Toast.LENGTH_SHORT).show()
}
}
override fun onBillingServiceDisconnected() {
Toast.makeText(requireActivity(), "Service Disconnected ", Toast.LENGTH_SHORT).show()
}
})
}
}
private fun initiatePurchase() {
val skuList: MutableList<String> = ArrayList()
skuList.add(ITEM_SKU_SUBSCRIBE)
val params = SkuDetailsParams.newBuilder()
params.setSkusList(skuList).setType(BillingClient.SkuType.SUBS)
val billingResult = billingClient!!.isFeatureSupported(BillingClient.FeatureType.SUBSCRIPTIONS)
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
billingClient!!.querySkuDetailsAsync(params.build()
) { billingResult, skuDetailsList ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
if (skuDetailsList != null && skuDetailsList.size > 0) {
val flowParams = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetailsList[0])
.build()
billingClient!!.launchBillingFlow(requireActivity(), flowParams)
} else {
//try to add subscription item "sub_example" in google play console
Toast.makeText(requireActivity(), "Item not Found", Toast.LENGTH_SHORT).show()
}
} else {
Toast.makeText(requireActivity(),
" Error " + billingResult.debugMessage, Toast.LENGTH_SHORT).show()
}
}
} else {
Toast.makeText(requireActivity(),
"Sorry Subscription not Supported. Please Update Play Store", Toast.LENGTH_SHORT).show()
}
}
override fun onPurchasesUpdated(billingResult: BillingResult, purchases: List<Purchase>?) {
//if item subscribed
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
handlePurchases(purchases)
}
else if (billingResult.responseCode == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) {
val queryAlreadyPurchasesResult = billingClient!!.queryPurchases(BillingClient.SkuType.SUBS)
val alreadyPurchases = queryAlreadyPurchasesResult.purchasesList
alreadyPurchases?.let { handlePurchases(it) }
}
else if (billingResult.responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
Toast.makeText(requireActivity(), "Purchase Canceled", Toast.LENGTH_SHORT).show()
}
else {
Toast.makeText(requireActivity(), "Error " + billingResult.debugMessage, Toast.LENGTH_SHORT).show()
}
}
fun handlePurchases(purchases: List<Purchase>) {
for (purchase in purchases) {
//if item is purchased
if (ITEM_SKU_SUBSCRIBE == purchase.sku && purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
if (!verifyValidSignature(purchase.originalJson, purchase.signature)) {
// Invalid purchase
// show error to user
Toast.makeText(requireActivity(), "Error : invalid Purchase", Toast.LENGTH_SHORT).show()
return
}
// else purchase is valid
//if item is purchased and not acknowledged
if (!purchase.isAcknowledged) {
val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.purchaseToken)
.build()
billingClient!!.acknowledgePurchase(acknowledgePurchaseParams, ackPurchase)
} else {
// Grant entitlement to the user on item purchase
// restart activity
if (!subscribeValueFromPref) {
saveSubscribeValueToPref(true)
Toast.makeText(requireActivity(), "Item Purchased", Toast.LENGTH_SHORT).show()
recreate(requireActivity())
}
}
} else if (ITEM_SKU_SUBSCRIBE == purchase.sku && purchase.purchaseState == Purchase.PurchaseState.PENDING) {
Toast.makeText(requireActivity(),
"Purchase is Pending. Please complete Transaction", Toast.LENGTH_SHORT).show()
} else if (ITEM_SKU_SUBSCRIBE == purchase.sku && purchase.purchaseState == Purchase.PurchaseState.UNSPECIFIED_STATE) {
saveSubscribeValueToPref(false)
subscribeAbstract.visibility = View.VISIBLE
Toast.makeText(requireActivity(), "Purchase Status Unknown", Toast.LENGTH_SHORT).show()
}
}
}
var ackPurchase = AcknowledgePurchaseResponseListener { billingResult ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
//if purchase is acknowledged
// Grant entitlement to the user. and restart activity
saveSubscribeValueToPref(true)
recreate(requireActivity())
}
}
/**
* Verifies that the purchase was signed correctly for this developer's public key.
*
* Note: It's strongly recommended to perform such check on your backend since hackers can
* replace this method with "constant true" if they decompile/rebuild your app.
*
*/
private fun verifyValidSignature(signedData: String, signature: String): Boolean {
return try {
// To get key go to Developer Console > Select your app > Development Tools > Services & APIs.
val base64Key = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhBlajrk5nNjpRTPJjDtGxgtgAeKijz3Wc0KrRKKSCxxsViHl7DhsI+sUZfk4Y1jxSg4/3W1uRo/0UASM77XfJIq34bK9KYgoSAGYSuH8Z+4fK/MrPz7dHhsljkAi4GZkv8x9VhZdDdpn2GSHVFaxs8c+HBOFp9aWAErHrQhi9/7fYf39pQSTC3WkVcy9xNDZxiiKTfDN3dyEvS0XQ617ZJwqDuRdkU5Aw9+R8r+oXyURV/ekgCQkWfCUaTp/jWdySOIcR87Bde24lQAXbvJaL5uAYI4zPwO4sIP1AbXLuDtv3N2rFVmP/1cML/NHDcfI5FOoStz88jzJU26Ngpqu1QIDAQAB"
Security.verifyPurchase(base64Key, signedData, signature)
} catch (e: IOException) {
false
}
}
override fun onDestroy() {
super.onDestroy()
if (billingClient != null) {
billingClient!!.endConnection()
}
}
companion object {
const val PREF_FILE = "MyPref"
const val SUBSCRIBE_KEY = "subscribe"
const val ITEM_SKU_SUBSCRIBE = "abstract_wallpapers"
}
override fun onItemClick(item: String) {
}
}
Adapter class
class AbstractAdapter(private val mContext: Context, private val abstractList: ArrayList<Abstract>, private val click: OnItemClickListenerAbstract ) :
RecyclerView.Adapter<AbstractAdapter.ViewHolder>() {
interface OnItemClickListenerAbstract{
fun onItemClick(item:String)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.abstract_image_view, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
Glide.with(mContext)
.load(abstractList[position].abstract)
.into(holder.imageView)
holder.imageView.setOnClickListener {
val intent = Intent(mContext, AbstractPreview::class.java)
intent.putExtra("abstract", abstractList[position].abstract.toString())
Toast.makeText(mContext, "Fullscreen view", Toast.LENGTH_SHORT).show()
mContext.startActivity(intent)
}
holder.downloadBtn.setOnClickListener {
abstractList.get(position).abstract?.let { it1 -> click.onItemClick(it1) }
}
}
override fun getItemCount(): Int {
return abstractList.size
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val imageView: ImageView = itemView.findViewById(R.id.abstractImageView)
val downloadBtn: Button = itemView.findViewById(R.id.abstractDownloadBtn)
}
companion object
}
You have 3 parameters for the class AbstractAdapter
class AbstractAdapter(private val mContext: Context, private val abstractList: ArrayList<Abstract>, private val click: OnItemClickListenerAbstract )
but you are instantiating the object with only two parameters
AbstractAdapter(requireActivity(),it)
It's expecting the third parameter for the clickListener
AbstractAdapter(requireActivity(),it,this)
(send the Fragment as the third param)

Please help kotlin error 'No value passed for parameter'

I have a chat application. But I am getting such error in adapters.
Error: No value passed for parameter 'itemView2' What I'm trying to do is to display the item named itemView2 in the xml page. I am not very proficient in Kotlin language. That's why I need help.
This is MessagingAdapter.kt
class MessagingAdapter(private val messages: OrderedRealmCollection<Message>, autoUpdate: Boolean,
private val context: Context, private val lifecycleOwner: LifecycleOwner, var user: User, private val myThumbImg: String,
private val selectedItems: LiveData<List<Message>>,
private val progressMap: LiveData<Map<String, Int>>, private val audibleState: LiveData<Map<String, AudibleState>>)
: RealmRecyclerViewAdapter<Message, RecyclerView.ViewHolder>(messages, autoUpdate)
, StickyHeaderAdapter<RecyclerView.ViewHolder> {
private val interaction = context as? Interaction?
private val contactHolderInteraction = context as? ContactHolderInteraction?
private val audibleHolderInteraction = context as? AudibleInteraction?
//timestamps to implement the date header
var timestamps = HashMap<Int, Long>()
var lastTimestampPos = 0
//date header
override fun getHeaderId(position: Int): Long {
return if (timestamps.containsKey(position)) {
timestamps[position] ?: 0
} else 0
}
//date header
override fun onCreateHeaderViewHolder(parent: ViewGroup): RecyclerView.ViewHolder? {
val view = LayoutInflater.from(parent.context).inflate(R.layout.row_day, parent, false)
return HeaderHolder(view)
}
//date header
override fun onBindHeaderViewHolder(viewholder: RecyclerView.ViewHolder?, position: Int) {
val mHolder = viewholder as HeaderHolder?
//if there are no timestamps in this day then hide the header
//otherwise show it
val headerId = getHeaderId(position)
if (headerId == 0L) mHolder?.header?.visibility = View.GONE else {
val formatted = TimeHelper.getChatTime(headerId)
mHolder?.header?.text = formatted
}
}
override fun getItemCount() = messages.size
override fun getItemViewType(position: Int): Int {
val message = messages[position]
return message.type
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
// check the type of view and return holder
return getHolderByType(parent, viewType)
}
override fun onBindViewHolder(mHolder: RecyclerView.ViewHolder, position: Int) {
//get itemView type
val type = getItemViewType(position)
val message = messages[position]
when (type) {
MessageType.SENT_TEXT -> {
val sentTextHolder = mHolder as SentTextHolder
initHolder(sentTextHolder)
sentTextHolder.bind(message, user)
}
MessageType.SENT_IMAGE -> {
val sentImageHolder = mHolder as SentImageHolder
initHolder(sentImageHolder)
sentImageHolder.bind(message, user)
}
MessageType.SENT_VOICE_MESSAGE -> {
val sentVoiceMessageHolder = mHolder as SentVoiceMessageHolder
initHolder(sentVoiceMessageHolder)
initAudibleHolder(sentVoiceMessageHolder)
sentVoiceMessageHolder.bind(message, user)
}
MessageType.SENT_VIDEO -> {
val sentVideoMessageHolder = mHolder as SentVideoMessageHolder
initHolder(sentVideoMessageHolder)
sentVideoMessageHolder.bind(message, user)
}
MessageType.SENT_FILE -> {
val sentFileHolder = mHolder as SentFileHolder
initHolder(sentFileHolder)
sentFileHolder.bind(message, user)
}
MessageType.SENT_AUDIO -> {
val sentAudioHolder = mHolder as SentAudioHolder
initHolder(sentAudioHolder)
initAudibleHolder(sentAudioHolder)
sentAudioHolder.bind(message, user)
}
MessageType.SENT_CONTACT -> {
val sentContactHolder = mHolder as SentContactHolder
initHolder(sentContactHolder)
initContactHolder(sentContactHolder)
sentContactHolder.bind(message, user)
}
MessageType.SENT_LOCATION -> {
val sentLocationHolder = mHolder as SentLocationHolder
initHolder(sentLocationHolder)
sentLocationHolder.bind(message, user)
}
MessageType.SENT_STICKER -> {
val sentStickerHolder = mHolder as SentStickerHolder
initHolder(sentStickerHolder)
sentStickerHolder.bind(message, user)
}
MessageType.RECEIVED_TEXT -> {
val holder = mHolder as ReceivedTextHolder
initHolder(holder)
holder.bind(message, user)
}
MessageType.RECEIVED_IMAGE -> {
val receivedImageHolder = mHolder as ReceivedImageHolder
initHolder(receivedImageHolder)
receivedImageHolder.bind(message, user)
}
MessageType.RECEIVED_VOICE_MESSAGE -> {
val receivedVoiceMessageHolder = mHolder as ReceivedVoiceMessageHolder
initHolder(receivedVoiceMessageHolder)
initAudibleHolder(receivedVoiceMessageHolder)
receivedVoiceMessageHolder.bind(message, user)
}
MessageType.RECEIVED_VIDEO -> {
val receivedVideoMessageHolder = mHolder as ReceivedVideoMessageHolder
initHolder(receivedVideoMessageHolder)
receivedVideoMessageHolder.bind(message, user)
}
MessageType.RECEIVED_FILE -> {
val receivedFileHolder = mHolder as ReceivedFileHolder
initHolder(receivedFileHolder)
receivedFileHolder.bind(message, user)
}
MessageType.RECEIVED_AUDIO -> {
val receivedAudioHolder = mHolder as ReceivedAudioHolder
initHolder(receivedAudioHolder)
initAudibleHolder(receivedAudioHolder)
receivedAudioHolder.bind(message, user)
}
MessageType.RECEIVED_CONTACT -> {
val receivedContactHolder = mHolder as ReceivedContactHolder
initHolder(receivedContactHolder)
initContactHolder(receivedContactHolder)
receivedContactHolder.bind(message, user)
}
MessageType.RECEIVED_LOCATION -> {
val receivedLocationHolder = mHolder as ReceivedLocationHolder
initHolder(receivedLocationHolder)
receivedLocationHolder.bind(message, user)
}
MessageType.SENT_DELETED_MESSAGE -> {
val sentDeletedMessageHolder = mHolder as SentDeletedMessageHolder
sentDeletedMessageHolder.bind(message, user)
}
MessageType.RECEIVED_DELETED_MESSAGE -> {
val receivedDeletedMessageHolder = mHolder as ReceivedDeletedMessageHolder
receivedDeletedMessageHolder.bind(message, user)
}
MessageType.GROUP_EVENT -> {
val groupEventHolder = mHolder as GroupEventHolder
groupEventHolder.bind(message, user)
}
MessageType.RECEIVED_STICKER -> {
val receivedStickerHolder = mHolder as ReceivedStickerHolder
initHolder(receivedStickerHolder)
receivedStickerHolder.bind(message, user)
}
else -> {
val notSupportedTypeHolder = mHolder as? NotSupportedTypeHolder
notSupportedTypeHolder?.bind(message, user)
}
}
}
private fun initHolder(baseHolder: BaseHolder) {
baseHolder.selectedItems = selectedItems
baseHolder.progressMap = progressMap
baseHolder.lifecycleOwner = lifecycleOwner
baseHolder.interaction = interaction
}
private fun initAudibleHolder(audibleBase: AudibleBase) {
audibleBase.audibleInteraction = audibleHolderInteraction
audibleBase.audibleState = audibleState
}
private fun initContactHolder(contactHolderBase: ContactHolderBase) {
contactHolderBase.contactHolderInteraction = contactHolderInteraction
}
private fun distinctMessagesTimestamps() {
for (i in messages.indices) {
val timestamp = messages[i].timestamp.toLong()
if (i == 0) {
timestamps[i] = timestamp
lastTimestampPos = i
} else {
val oldTimestamp = messages[i - 1].timestamp.toLong()
if (!TimeHelper.isSameDay(timestamp, oldTimestamp)) {
timestamps[i] = timestamp
lastTimestampPos = i
}
}
}
}
//update timestamps if needed when a new message inserted
fun messageInserted() {
val index = messages.size - 1
val newTimestamp = messages[index].timestamp.toLong()
if (timestamps.isEmpty()) {
timestamps[index] = newTimestamp
lastTimestampPos = index
return
}
val lastTimestamp = timestamps[lastTimestampPos]!!
if (!TimeHelper.isSameDay(lastTimestamp, newTimestamp)) {
timestamps[index] = newTimestamp
lastTimestampPos = index
}
}
private fun getHolderByType(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
when (viewType) {
MessageType.DAY_ROW -> return TimestampHolder(LayoutInflater.from(parent.context).inflate(R.layout.row_day, parent, false))
MessageType.SENT_DELETED_MESSAGE -> return SentDeletedMessageHolder(context, LayoutInflater.from(parent.context).inflate(R.layout.row_sent_deleted_message, parent, false))
MessageType.RECEIVED_DELETED_MESSAGE -> return ReceivedDeletedMessageHolder(context, LayoutInflater.from(parent.context).inflate(R.layout.row_received_deleted_message, parent, false))
MessageType.SENT_TEXT -> return SentTextHolder(context, LayoutInflater.from(parent.context).inflate(R.layout.row_sent_message_text, parent, false))
MessageType.SENT_IMAGE -> return SentImageHolder(context, LayoutInflater.from(parent.context).inflate(R.layout.row_sent_message_img, parent, false))
MessageType.RECEIVED_TEXT -> return ReceivedTextHolder(context, LayoutInflater.from(parent.context).inflate(R.layout.row_received_message_text, parent, false)) //ERROR HERE
MessageType.RECEIVED_IMAGE -> return ReceivedImageHolder(context, LayoutInflater.from(parent.context).inflate(R.layout.row_received_message_img, parent, false))
MessageType.SENT_VOICE_MESSAGE -> return SentVoiceMessageHolder(context, LayoutInflater.from(parent.context).inflate(R.layout.row_sent_voice_message, parent, false), myThumbImg)
MessageType.RECEIVED_VOICE_MESSAGE -> return ReceivedVoiceMessageHolder(context, LayoutInflater.from(parent.context).inflate(R.layout.row_received_message_voice, parent, false))
MessageType.RECEIVED_VIDEO -> return ReceivedVideoMessageHolder(context, LayoutInflater.from(parent.context).inflate(R.layout.row_received_message_video, parent, false))
MessageType.SENT_VIDEO -> return SentVideoMessageHolder(context, LayoutInflater.from(parent.context).inflate(R.layout.row_sent_message_video, parent, false))
MessageType.SENT_FILE -> return SentFileHolder(context, LayoutInflater.from(parent.context).inflate(R.layout.row_sent_file, parent, false))
MessageType.RECEIVED_FILE -> return ReceivedFileHolder(context, LayoutInflater.from(parent.context).inflate(R.layout.row_received_file, parent, false))
MessageType.SENT_AUDIO -> return SentAudioHolder(context, LayoutInflater.from(parent.context).inflate(R.layout.row_sent_audio, parent, false))
MessageType.RECEIVED_AUDIO -> return ReceivedAudioHolder(context, LayoutInflater.from(parent.context).inflate(R.layout.row_received_audio, parent, false))
MessageType.SENT_CONTACT -> return SentContactHolder(context, LayoutInflater.from(parent.context).inflate(R.layout.row_sent_contact, parent, false))
MessageType.RECEIVED_CONTACT -> return ReceivedContactHolder(context, LayoutInflater.from(parent.context).inflate(R.layout.row_received_contact, parent, false))
MessageType.SENT_LOCATION -> return SentLocationHolder(context, LayoutInflater.from(parent.context).inflate(R.layout.row_sent_location, parent, false))
MessageType.RECEIVED_LOCATION -> return ReceivedLocationHolder(context, LayoutInflater.from(parent.context).inflate(R.layout.row_received_location, parent, false))
MessageType.GROUP_EVENT -> return GroupEventHolder(context, LayoutInflater.from(parent.context).inflate(R.layout.row_group_event, parent, false))
MessageType.SENT_STICKER -> return SentStickerHolder(context, LayoutInflater.from(parent.context).inflate(R.layout.row_sent_sticker, parent, false))
MessageType.RECEIVED_STICKER -> return ReceivedStickerHolder(context, LayoutInflater.from(parent.context).inflate(R.layout.row_received_sticker, parent, false))
}
return NotSupportedTypeHolder(context, LayoutInflater.from(parent.context).inflate(R.layout.row_not_supported, parent, false))
}
init {
distinctMessagesTimestamps()
}
}
This is ReceivedTextHolder.kt
class ReceivedTextHolder(context: Context, itemView: View,itemView2: View) : BaseReceivedHolder(context,itemView) {
private var tvMessageContent: AXEmojiTextView = itemView.findViewById(R.id.tv_message_content)
private val circleImg: CircleImageView = itemView2.findViewById<View>(R.id.voice_circle_img_text) as CircleImageView
override fun bind(message: Message,user: User) {
super.bind(message,user)
tvMessageContent.text = message.content
loadUserPhoto(user,message.fromId, circleImg)
}
private fun loadUserPhoto(user:User,fromId: String, imageView: ImageView) {
//if it's a group load the user image
if (user.isGroupBool && user.group.users != null) {
val mUser = ListUtil.getUserById(fromId, user.group.users)
if (mUser != null && mUser.thumbImg != null) {
Glide.with(context).load(mUser.thumbImg).into(imageView)
}
} else {
if (user.thumbImg != null) Glide.with(context).load(user.thumbImg).into(imageView)
}
}
}
Please help guys :(
What I'm trying to do is to display the item named itemView2 in the xml page.
What the error means is that you're trying to call a function that asks for a parameter named itemView2, but you didn't pass that parameter. Usually this happens when you provide too few arguments when you call the method.
In this specific case, the method in question is actually the constructor of ReceivedTextHolder. As you can see, the constructor is declared to take 3 parameters (context, itemView, and itemView2):
class ReceivedTextHolder(context: Context, itemView: View,itemView2: View)
But when you call this constructor, you only give 2 arguments:
return ReceivedTextHolder(context, LayoutInflater.from(parent.context).inflate(R.layout.row_received_message_text, parent, false)) //ERROR HERE
Here, the first argument (context) receives the value context, and the second argument (itemView) receives the value of the expression:
LayoutInflater.from(parent.context).inflate(R.layout.row_received_message_text, parent, false)
But you're missing the 3rd argument. The 3rd would probably look like the 2nd one. You just need to find and inflate the correct view for that.

Incompatible types: Int and Article

I am developing news app I have implemented multipleview types in recyclerview adapter class but I am getting following error
Incompatible types: Int and Article
below BBCSportAdapter class where I have implemented multipleview types
#Suppress("UNREACHABLE_CODE")
class BBCSportAdapter(private val listViewType: List<Int>) : RecyclerView.Adapter<BBCSportAdapter.MyViewHolder>() {
companion object {
val ITEM_A = 1
var ITEM_B = 2
}
var articleList: List<Article> = listOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val inflater =
LayoutInflater.from(parent.context)
return when (viewType) {
ITEM_A -> ViewHolderItemA(inflater.inflate(R.layout.bbc_sport_list, null))
else -> {
ViewHolderItemB(inflater.inflate(R.layout.bbc_sport_item, null))
}
}
}
#SuppressLint("NewApi")
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val viewType = articleList[position]
when (viewType) {
ITEM_A -> {
val viewHolderA = holder as ViewHolderItemA
Picasso.get().load(articleList[position].urlToImage)
.into(viewHolderA.topFlameImageView)
}else -> {
val viewHolderB = holder as ViewHolderItemB
}
}
}
override fun getItemCount(): Int {
return articleList.size
}
// holder.articleTitle.text = articleList[position].title
// holder . articleSourceName . text = articleList [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 setMovieListItems(articleList: List<Article>) {
this.articleList = articleList
notifyDataSetChanged()
}
open inner class MyViewHolder(itemView: View?) : RecyclerView.ViewHolder(itemView!!){}
inner class ViewHolderItemA(itemView: View) : MyViewHolder(itemView) {
val topFlameImageView: ImageView = itemView.findViewById(R.id.topFlameImageView)
}
inner class ViewHolderItemB(itemView: View?) : MyViewHolder(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)
}
}
I have followed this link https://github.com/CoderJava/Multiple-View-Type-RecyclerView-Kotlin-Android/blob/master/app/src/main/java/com/ysn/multipleviewtypeexample/AdapterRecyclerView.kt
In your onBindViewHolder articleList is list of artical but in your when statement you are comparing Article with an Int i.e. ITEM_A, which is wrong. Instead you should have some type in your article object and comparison is based on that type. Moreover you have not implemented getItemViewType() where you can make decision which view will be inflated. In your case viewType in parameter of oncreateViewHolder will always return 0 and else condition will be executed always and you will always have single type of view.