Hello everyone I've created a RecyclerView and get the data with AsyncTask. In the first step I want is to display 10 first data and load more 10 data until it reach the last data.
After that I want to implement a progressbar as a footer to load data.
here is my code for my MutationRecyclerViewAdapter
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_mutation, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.mItem = mValues[position]
holder.itemView.text_transaction_type.text = getTransactionTypeString(mValues[position].transactionType)
holder.itemView.text_transaction_number.text = mValues[position].transactionID
holder.itemView.text_transaction_date.text = AppUtil.getDefaultDateFormat(mValues[position].transactionDate)
holder.itemView.text_balance.text = mValues[position].balance
holder.itemView.text_amount.text = mValues[position].amount
holder.itemView.text_merchant_name.text = mValues[position].merchant
if(mValues[position].transactionType != null) {
when (mValues[position].transactionType) {
"PR", "ST" -> {
holder.itemView.image_transaction_icon.setImageResource(R.drawable.ic_action_purchase)
holder.itemView.image_transaction_icon.background = ContextCompat.getDrawable(mContext!!, R.drawable.bg_circle)
holder.itemView.text_transaction_type.setTextColor(ContextCompat.getColor(mContext!!, R.color.orange))
holder.itemView.text_product_name.text = holder.mItem!!.merchant
holder.itemView.text_amount.setTextColor(ContextCompat.getColor(mContext!!, R.color.orange))
//holder.itemView.text_product_name.visibility = View.VISIBLE
holder.itemView.image_balance.setImageResource(R.drawable.ic_min_orange)
}
"RD" -> {
holder.itemView.image_transaction_icon.setImageResource(R.drawable.ic_action_withdraw_rotated)
holder.itemView.image_transaction_icon.background = ContextCompat.getDrawable(mContext!!, R.drawable.bg_circle)
holder.itemView.text_transaction_type.setTextColor(ContextCompat.getColor(mContext!!, R.color.orange))
holder.itemView.image_balance.setImageResource(R.drawable.ic_plus)
holder.itemView.text_amount.setTextColor(ContextCompat.getColor(mContext!!, R.color.orange))
}
"CI" -> {
holder.itemView.text_transaction_type.setTextColor(ContextCompat.getColor(mContext!!, R.color.green_grass_dark))
holder.itemView.image_transaction_icon.background = ContextCompat.getDrawable(mContext, R.drawable.bg_circle_green)
holder.itemView.image_transaction_icon.setImageResource(R.drawable.ic_action_cash_in)
holder.itemView.image_balance.setImageResource(R.drawable.ic_plus_green)
holder.itemView.text_amount.setTextColor(ContextCompat.getColor(mContext, R.color.green_grass_dark))
}
"CO", "FCO", "XX" -> {
holder.itemView.image_transaction_icon.background = ContextCompat.getDrawable(mContext!!, R.drawable.bg_circle)
holder.itemView.image_transaction_icon.setImageResource(R.drawable.ic_action_withdraw_rotated)
holder.itemView.text_transaction_type.setTextColor(ContextCompat.getColor(mContext, R.color.orange))
holder.itemView.image_balance.setImageResource(R.drawable.ic_min_orange)
holder.itemView.text_amount.setTextColor(ContextCompat.getColor(mContext, R.color.orange))
}
"AD" -> {
holder.itemView.image_transaction_icon.setImageResource(R.drawable.ic_action_adjustment)
holder.itemView.image_transaction_icon.background = ContextCompat.getDrawable(mContext!!, R.drawable.bg_circle_gray)
holder.itemView.text_transaction_type.setTextColor(ContextCompat.getColor(mContext, R.color.gray_dark))
holder.itemView.image_balance.setImageResource(R.drawable.ic_plus)
holder.itemView.text_amount.setTextColor(ContextCompat.getColor(mContext, R.color.gray_dark))
}
"RBY"->{
holder.itemView.image_transaction_icon.setImageResource(R.drawable.ic_action_adjustment)
holder.itemView.image_transaction_icon.background = ContextCompat.getDrawable(mContext!!, R.drawable.bg_circle_gray)
holder.itemView.text_transaction_type.setTextColor(ContextCompat.getColor(mContext, R.color.gray_dark))
holder.itemView.image_balance.setImageResource(R.drawable.ic_plus)
holder.itemView.text_amount.setTextColor(ContextCompat.getColor(mContext, R.color.gray_dark))
}
"TO" -> {
holder.itemView.image_transaction_icon.background = ContextCompat.getDrawable(mContext!!, R.drawable.bg_circle_blue)
holder.itemView.image_transaction_icon.setImageResource(R.drawable.ic_action_transfer)
holder.itemView.text_transaction_type.setTextColor(ContextCompat.getColor(mContext!!, R.color.blue_foreground))
holder.itemView.image_balance.setImageResource(R.drawable.ic_minus_blue)
holder.itemView.text_amount.setTextColor(ContextCompat.getColor(mContext, R.color.blue_foreground))
}
"TI", "BY" -> {
holder.itemView.image_transaction_icon.background = ContextCompat.getDrawable(mContext!!, R.drawable.bg_circle_green)
holder.itemView.image_transaction_icon.setImageResource(R.drawable.ic_action_transfer_in)
holder.itemView.text_transaction_type.setTextColor(ContextCompat.getColor(mContext!!, R.color.green_grass_dark))
holder.itemView.image_balance.setImageResource(R.drawable.ic_plus_green)
holder.itemView.text_amount.setTextColor(ContextCompat.getColor(mContext, R.color.green_grass_dark))
}
else -> {
holder.itemView.image_transaction_icon.setImageResource(R.drawable.ic_action_adjustment)
holder.itemView.image_transaction_icon.background = ContextCompat.getDrawable(mContext!!, R.drawable.bg_circle_gray)
holder.itemView.text_transaction_type.setTextColor(ContextCompat.getColor(mContext, R.color.gray_dark))
holder.itemView.image_balance.visibility = View.INVISIBLE
holder.itemView.text_amount.setTextColor(ContextCompat.getColor(mContext, R.color.gray_dark))
}
}
} else {
holder.itemView.image_transaction_icon.setImageResource(R.drawable.ic_action_adjustment)
holder.itemView.image_transaction_icon.background = ContextCompat.getDrawable(mContext!!, R.drawable.bg_circle_gray)
holder.itemView.text_transaction_type.setTextColor(ContextCompat.getColor(mContext, R.color.gray_dark))
holder.itemView.image_balance.visibility = View.INVISIBLE
holder.itemView.text_amount.setTextColor(ContextCompat.getColor(mContext, R.color.gray_dark))
}
holder.mView.setOnClickListener {
mListener?.onMutationFragmentInteraction(holder.mItem!!)
}
holder.mView.setOnLongClickListener {
mListener?.onLongMutationFragmentInteraction(holder.mItem!!)
false
}
}
private fun getTransactionTypeString(type: String): String {
return when(type) {
"PR" -> mContext!!.getString(R.string.purchase)
"CI" -> mContext!!.getString(R.string.cash_in)
"CO" -> mContext!!.getString(R.string.cash_out)
"RD" -> mContext!!.getString(R.string.redeem)
"TI" -> mContext!!.getString(R.string.transfer_in)
"TO" -> mContext!!.getString(R.string.transfer_out)
"FCO" -> mContext!!.getString(R.string.cash_out)
"XX" -> mContext!!.getString(R.string.cash_out)
"BY" -> mContext!!.getString(R.string.purchase)
"ST" -> mContext!!.getString(R.string.settlement)
"AD" -> mContext!!.getString(R.string.adjustment)
"RBY" ->mContext!!.getString(R.string.rby)
else -> return type
}
}
override fun getItemCount(): Int {
return mValues.size
}
inner class ViewHolder(val mView: View) : RecyclerView.ViewHolder(mView) {
var mItem: MutationItem? = null
}
and this my MutationFragment
private var maxItemSize = -1
private var mListener: OnMutationFragmentInteractionListener? = null
lateinit var adapter : MutationRecyclerViewAdapter
lateinit var refreshButton : ImageView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (arguments != null) {
maxItemSize = arguments?.getInt(MAX_ITEMS_SIZE, -1)!!
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_mutation, container, false)
(activity as AppCompatActivity).supportActionBar?.title = "MutationFragment"
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
MutationContent.ITEMS.clear()
GetTransactionTask(context!!, this).execute()
mutation_list.layoutManager = LinearLayoutManager(context)
adapter = MutationRecyclerViewAdapter(MutationContent.getSavedTransactions(context, maxItemSize), mListener, context)
refreshLayout.apply {
scrollUpChild = mutation_list
setOnRefreshListener {
isRefreshing = false
GetTransactionTask(context!!, this#MutationFragment).execute()
}
}
}
fun toggleView(isEmpty : Boolean){
if(isEmpty){
progress_bar?.visibility = View.GONE
mutation_list?.visibility = View.GONE
//refreshButton.visibility = View.GONE
text_no_mutation?.visibility = View.VISIBLE
}
else{
progress_bar?.visibility = View.GONE
mutation_list?.visibility = View.VISIBLE
//refreshButton?.visibility = View.GONE
text_no_mutation?.visibility = View.GONE
}
activity?.invalidateOptionsMenu()
}
override fun onGetTransactionSuccess(response: ResponseRequestMutasiSaldo) {
MutationContent.import(context, response, maxItemSize)
adapter = MutationRecyclerViewAdapter(MutationContent.getSavedTransactions(context, maxItemSize),
mListener, context)
mutation_list?.adapter = adapter
if(adapter.mValues.isEmpty()) {
text_no_mutation?.setText(getString(R.string.no_mutation))
} else {
text_no_mutation?.setText(getString(R.string.load_mutation))
}
toggleView(adapter.mValues.isEmpty())
}
override fun onAttach(context: Context?) {
super.onAttach(context)
if (context is OnMutationFragmentInteractionListener) {
mListener = context
} else {
throw RuntimeException(context!!.toString() + " must implement OnSettingFragmentInteractionListener")
}
}
override fun onDetach() {
super.onDetach()
mListener = null
}
override fun onTransactionFailed(response: ResponseRequestMutasiSaldo) {
adapter = MutationRecyclerViewAdapter(
MutationContent.getSavedTransactions(context, maxItemSize),
mListener, context)
mutation_list?.adapter = adapter
toggleView(adapter.mValues.isEmpty())
/*if(activity != null)
Toasty.error(activity!!,response.errorMessage).show()*/
}
interface OnMutationFragmentInteractionListener {
fun onMutationFragmentInteraction(item: MutationItem)
fun onLongMutationFragmentInteraction(item: MutationItem)
}
companion object {
val MAX_ITEMS_SIZE = "MAX_ITEM_SIZE"
}
and this my Asynctask Class
override fun doInBackground(vararg p0: Void?): ResponseRequestMutasiSaldo? {
return ResourceService(context).requestMutasiSaldo()
}
override fun onPostExecute(result: ResponseRequestMutasiSaldo?) {
if(result != null && (result.error.equals(AppUtil.RESPONSE_ERROR_CODE_EXPIRY) || result.success.equals(AppUtil.RESPONSE_SUCCESS_CODE_EXPIRY))){
EventBus.getDefault().post(SessionExpiry(result.message?:""))
return
}
if(result != null && result.success.equals("1")) {
listener.onGetTransactionSuccess(result)
}
if(result != null && result.error.equals("1")){
listener.onTransactionFailed(result)
return
}
}
I have successfully got all the data from web service and now I've tried, using EndlessRecyclerViewListener like in this documentation and using several references to achive my task. But the function AddOnScroll didn't work properly and I didn't understand well how this work. How do I do if I want to use EndlessRecyclerViewListener? Or is there any other way to achive this without using EndlessRecyclerViewListener class?
Use addOnScrollListener to do this.
val pageLimit: Int = 10//row returned each time on scroll
var noMoreItems: Boolean = false
use addOnScrollListener in your recyclerview like this.
dataRecyclerView.addOnScrollListener(object : androidx.recyclerview.widget.RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: androidx.recyclerview.widget.RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (!recyclerView.canScrollVertically(1)) {
if (!utility.isConnected(mContext)) {
Toast.makeText(mContext, "No internet", Toast.LENGTH_SHORT).show()
return
}
if (noMoreItems)
return
val lastPage: String
val nextPage: Int
if (dataArrayList.size > 0) {
lastPage = ((dataArrayList.size - 1) / pageLimit).toInt().toString()
nextPage = Integer.parseInt(lastPage) + 1
} else {
nextPage = 1;
}
loadRecords(nextPage.toString())
}
}
})
here in loadRecords() you should call your asynctask with page number.
set noMOreItems=true when number of rows returned is less than pageLimit.
Related
I'm currently following a news app tutorial and I have a problem. When I type in a keyword in the edit text widget, articles related to that keyword shows up in the recycler view but when I erase that keyword to type in another keyword, the articles (in the recycler view) from the previous search query doesn't update and even when I exit the search fragment and open it again,The recycler view remains stagnant instead of disappearing. Can anyone please take a look at my code and let me know what I've done wrong. Thanks in advance.
Here is my code:
Search Fragment
`class SearchNewsFragment : Fragment(R.layout.fragment_search_news) {
lateinit var viewModel: NewsViewModel
lateinit var newsAdapter: NewsAdapter
val TAG = "SearchNewsFragment"
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = (activity as NewsActivity).viewModel
setupRecyclerView()
newsAdapter.setOnItemClickListener {
val bundle = Bundle().apply {
putSerializable("article", it)
}
findNavController().navigate(
R.id.action_searchNewsFragment_to_articleFragment,
bundle
)
}
var job: Job? = null
etSearch.addTextChangedListener { editable ->
job?.cancel()
job = MainScope().launch {
delay(SEARCH_NEWS_TIME_DELAY)
editable?.let {
if(editable.toString().isNotEmpty()) {
viewModel.searchNews(editable.toString())
}
}
}
}
viewModel.searchNews.observe(viewLifecycleOwner, Observer { response ->
when(response) {
is Resource.Success -> {
hideProgressBar()
hideErrorMessage()
response.data?.let { newsResponse ->
newsAdapter.differ.submitList(newsResponse.articles.toList())
val totalPages = newsResponse.totalResults / Constants.QUERY_PAGE_SIZE + 2
isLastPage = viewModel.searchNewsPage == totalPages
if(isLastPage) {
rvSearchNews.setPadding(0, 0, 0, 0)
}
}
}
is Resource.Error -> {
hideProgressBar()
response.message?.let { message ->
Toast.makeText(activity, "An error occured: $message", Toast.LENGTH_LONG).show()
showErrorMessage(message)
}
}
is Resource.Loading -> {
showProgressBar()
}
}
})
btnRetry.setOnClickListener {
if (etSearch.text.toString().isNotEmpty()) {
viewModel.searchNews(etSearch.text.toString())
} else {
hideErrorMessage()
}
}
}
private fun hideProgressBar() {
paginationProgressBar.visibility = View.INVISIBLE
isLoading = false
}
private fun showProgressBar() {
paginationProgressBar.visibility = View.VISIBLE
isLoading = true
}
private fun hideErrorMessage() {
itemErrorMessage.visibility = View.INVISIBLE
isError = false
}
private fun showErrorMessage(message: String) {
itemErrorMessage.visibility = View.VISIBLE
tvErrorMessage.text = message
isError = true
}
var isError = false
var isLoading = false
var isLastPage = false
var isScrolling = false
val scrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
val visibleItemCount = layoutManager.childCount
val totalItemCount = layoutManager.itemCount
val isNoErrors = !isError
val isNotLoadingAndNotLastPage = !isLoading && !isLastPage
val isAtLastItem = firstVisibleItemPosition + visibleItemCount >= totalItemCount
val isNotAtBeginning = firstVisibleItemPosition >= 0
val isTotalMoreThanVisible = totalItemCount >= Constants.QUERY_PAGE_SIZE
val shouldPaginate = isNoErrors && isNotLoadingAndNotLastPage && isAtLastItem && isNotAtBeginning &&
isTotalMoreThanVisible && isScrolling
if(shouldPaginate) {
viewModel.searchNews(etSearch.text.toString())
isScrolling = false
}
}
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if(newState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
isScrolling = true
}
}
}
private fun setupRecyclerView() {
newsAdapter = NewsAdapter()
rvSearchNews.apply {
adapter = newsAdapter
layoutManager = LinearLayoutManager(activity)
addOnScrollListener(this#SearchNewsFragment.scrollListener)
}
}
}`
SearchNewsAdapter
`class SearchNewsAdapter : RecyclerView.Adapter<SearchNewsAdapter.ArticleViewHolder>() {
// Inner class for viewHolder
inner class ArticleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
private val differCallback = object : DiffUtil.ItemCallback<Article>(){
override fun areItemsTheSame(oldItem: Article, newItem: Article): Boolean {
return oldItem.url== newItem.url
}
override fun areContentsTheSame(oldItem: Article, newItem: Article): Boolean {
return oldItem == newItem
}
}
val differ = AsyncListDiffer(this, differCallback)
//recyclerViewFunction
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArticleViewHolder {
return ArticleViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.search_article_preview,parent, false)
)
}
override fun getItemCount(): Int {
return differ.currentList.size
}
override fun onBindViewHolder(holder: ArticleViewHolder, position: Int) {
val article = differ.currentList[position]
holder.itemView.apply{
// Glide.with(this).load(article.urlToImage).into(ivArticleImage)
searchTitle.text = article.title
setOnClickListener{
onItemClickListener?.let{
it(article)
}
}
}
}
//item click listener to single article so that article fragment opens up the webview that shows our items
private var onItemClickListener: ((Article) -> Unit)? = null
fun setOnItemClickListener(listener:(Article) -> Unit){
onItemClickListener = listener
}
}`
NewsViewModel
`class NewsViewModel(
app: Application,
val newsRepository: NewsRepository
) : AndroidViewModel(app) {
val breakingNews: MutableLiveData<Resource<NewsResponse>> = MutableLiveData()
var breakingNewsPage = 1
var breakingNewsResponse: NewsResponse? = null
val searchNews: MutableLiveData<Resource<NewsResponse>> = MutableLiveData()
var searchNewsPage = 1
var searchNewsResponse: NewsResponse? = null
var newSearchQuery:String? = null
var oldSearchQuery:String? = null
init {
getBreakingNews("us")
}
fun getBreakingNews(countryCode: String) = viewModelScope.launch {
safeBreakingNewsCall(countryCode)
}
fun searchNews(searchQuery: String) = viewModelScope.launch {
safeSearchNewsCall(searchQuery)
}
private fun handleBreakingNewsResponse(response: Response<NewsResponse>) : Resource<NewsResponse> {
if(response.isSuccessful) {
response.body()?.let { resultResponse ->
breakingNewsPage++
if(breakingNewsResponse == null) {
breakingNewsResponse = resultResponse
} else {
val oldArticles = breakingNewsResponse?.articles
val newArticles = resultResponse.articles
oldArticles?.addAll(newArticles)
}
return Resource.Success(breakingNewsResponse ?: resultResponse)
}
}
return Resource.Error(response.message())
}
private fun handleSearchNewsResponse(response: Response<NewsResponse>) : Resource<NewsResponse> {
if(response.isSuccessful) {
response.body()?.let { resultResponse ->
if(searchNewsResponse == null || newSearchQuery != oldSearchQuery) {
searchNewsPage = 1
oldSearchQuery = newSearchQuery
searchNewsResponse = resultResponse
} else {
searchNewsPage++
val oldArticles = searchNewsResponse?.articles
val newArticles = resultResponse.articles
oldArticles?.addAll(newArticles)
}
return Resource.Success(searchNewsResponse ?: resultResponse)
}
}
return Resource.Error(response.message())
}
fun saveArticle(article: Article) = viewModelScope.launch {
newsRepository.upsert(article)
}
fun getSavedNews() = newsRepository.getSavedNews()
fun deleteArticle(article: Article) = viewModelScope.launch {
newsRepository.deleteArticle(article)
}
private suspend fun safeSearchNewsCall(searchQuery: String) {
newSearchQuery = searchQuery
searchNews.postValue(Resource.Loading())
try {
if(hasInternetConnection()) {
val response = newsRepository.searchNews(searchQuery, searchNewsPage)
searchNews.postValue(handleSearchNewsResponse(response))
} else {
searchNews.postValue(Resource.Error("No internet connection"))
}
} catch(t: Throwable) {
when(t) {
is IOException -> searchNews.postValue(Resource.Error("Network Failure"))
else -> searchNews.postValue(Resource.Error("Conversion Error"))
}
}
}
private suspend fun safeBreakingNewsCall(countryCode: String) {
breakingNews.postValue(Resource.Loading())
try {
if(hasInternetConnection()) {
val response = newsRepository.getBreakingNews(countryCode, breakingNewsPage)
breakingNews.postValue(handleBreakingNewsResponse(response))
} else {
breakingNews.postValue(Resource.Error("No internet connection"))
}
} catch(t: Throwable) {
when(t) {
is IOException -> breakingNews.postValue(Resource.Error("Network Failure"))
else -> breakingNews.postValue(Resource.Error("Conversion Error"))
}
}
}
private fun hasInternetConnection(): Boolean {
val connectivityManager = getApplication<NewsApplication>().getSystemService(
Context.CONNECTIVITY_SERVICE
) as ConnectivityManager
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val activeNetwork = connectivityManager.activeNetwork ?: return false
val capabilities = connectivityManager.getNetworkCapabilities(activeNetwork) ?: return false
return when {
capabilities.hasTransport(TRANSPORT_WIFI) -> true
capabilities.hasTransport(TRANSPORT_CELLULAR) -> true
capabilities.hasTransport(TRANSPORT_ETHERNET) -> true
else -> false
}
} else {
connectivityManager.activeNetworkInfo?.run {
return when(type) {
TYPE_WIFI -> true
TYPE_MOBILE -> true
TYPE_ETHERNET -> true
else -> false
}
}
}
return false
}
}
`
if(editable.toString().isNotEmpty()) { viewModel.searchNews(editable.toString()) }
this line of code prevent the empty query to be processed. So, when you delete everything from the edit text, it will do nothing, hence the result still the same.
even when I exit the search fragment and open it again,The recycler view remains stagnant instead of disappearing.
The search result is strored on NewsViewModel and because the ViewModel is initialized on the NewsActivity, it tied to the activity lifecycle. Even if you destroy the fragment, the search result (the whole ViewModel) will be kept because the activity is still alive. So, when you open back the search Fragment, the LiveData will give you the latest value.
I want to implement a quiz app where Question List are shown in a RecyclerView. I use CountDownTimer for tracking duration of the test. I want to disable clicking on Radio Button After Time is finished.
The problem I face, I can't disable clicking button from Adapter class. When onFinshed() is called, no value is added in result variable. That's great. I want that. But the problem is, it still possible to select from RadioButton whish is very bad for user experience.
Another Problem I facing is, when I checked radiobutton from view item number 1 and 2, the radio button of item number 9 and 10 got selected.
What I tried is given bellow
QuizTimer.kt
class QuizTimer(
private val updateCountdownText: (String)->Unit,
private val timesUp: ()->Unit
) : CountDownTimer(60000, 1000) {
override fun onTick(millisUntilFinished: Long) {
val minutes = (millisUntilFinished / 1000) / 60
val seconds = (millisUntilFinished / 1000) % 60
val formattedTimeInString: String = format(Locale.getDefault(), "%02d:%02d", minutes, seconds)
updateCountdownText(formattedTimeInString)
}
override fun onFinish() {
timesUp()
}
fun startCounting() {
start()
}
fun finishCounting() {
cancel()
}
}
Question.kt
data class Question(
#SerializedName("ques_id") val quesId: Int?,
#SerializedName("question") val question: String?,
#SerializedName("option_a") val optionA: String?,
#SerializedName("option_b") val optionB: String?,
#SerializedName("option_c") val optionC: String?,
#SerializedName("option_d") val optionD: String?,
#SerializedName("correct_answer") val correctAnswer: String?,
#SerializedName("marks") val marks: Int?,
var selectedAnswer: String?
)
QuestionViewHolder.kt
class QuestionViewHolder(private val bindingView: ItemQuestionBinding) : RecyclerView.ViewHolder(bindingView.root) {
fun bind(question: Question, onCheckedListener: (Question) -> Unit) {
bindingView.question = question
bindingView.root.questionHolder.setOnCheckedChangeListener { group, checkedId ->
when(checkedId) {
R.id.optionA -> question.selectedAnswer = "A"
R.id.optionB -> question.selectedAnswer = "B"
R.id.optionC -> question.selectedAnswer = "C"
R.id.optionD -> question.selectedAnswer = "D"
}
onCheckedListener(question)
}
}
fun getView() = bindingView.root
fun disableRadioButton() {
val questionViewHolderRadioGroup: RadioGroup = getView().questionHolder
for (i in 0 until questionViewHolderRadioGroup.childCount) {
(questionViewHolderRadioGroup.getChildAt(i) as RadioButton).isEnabled = false
}
}
}
QuestionAdapter.kt
class QuestionAdapter(private val onClickListener: (Question) -> Unit): RecyclerView.Adapter<QuestionViewHolder>() {
private var questionList: ArrayList<Question>? = ArrayList()
var isClickable: Boolean = true
lateinit var questionViewHolder: QuestionViewHolder
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QuestionViewHolder {
val view = ItemQuestionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return QuestionViewHolder(view)
}
override fun getItemCount(): Int {
return questionList?.size ?: 0
}
override fun onBindViewHolder(holder: QuestionViewHolder, position: Int) {
this.questionViewHolder = holder
holder.bind(questionList?.get(position)!!, onClickListener)
holder.getView().number_text_view.text = "${position+1}."
}
fun replaceData(reminderList: ArrayList<Question>) {
this.questionList = reminderList
notifyDataSetChanged()
}
fun disableRadioButton() {
questionViewHolder.disableRadioButton()
}
}
item_question.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="com.caretutors.api.serviceModel.tutorProfileResponse.Question"/>
<variable name="question" type="com.caretutors.api.serviceModel.tutorProfileResponse.Question" />
</data>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="#dimen/_5sdp"
android:layout_marginEnd="#dimen/_5sdp"
android:layout_marginTop="#dimen/_10sdp"
app:cardCornerRadius="#dimen/_12sdp"
app:cardElevation="0dp">
<View
android:id="#+id/line"
android:layout_width="#dimen/_4sdp"
android:layout_height="match_parent"
android:background="#color/colorAccent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="#dimen/_15sdp"
android:layout_marginEnd="#dimen/_10sdp"
android:layout_marginTop="#dimen/_10sdp"
android:layout_marginBottom="#dimen/_10sdp"
android:orientation="vertical">
<TextView
android:id="#+id/number_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="1."
android:textColor="#color/black_85_percent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/title_textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="#dimen/_5sdp"
android:text="#{question.question}"
android:textColor="#color/black_85_percent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="#+id/number_text_view"
app:layout_constraintTop_toTopOf="parent"
tools:text="What is the Capital of Bangladesh ?" />
<RadioGroup
android:id="#+id/questionHolder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/title_textView">
<RadioButton
android:id="#+id/optionA"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/_5sdp"
android:button="#drawable/custom_radio_button"
android:paddingStart="#dimen/_8sdp"
android:text="#{question.optionA}"
android:textColor="#color/black_50_percent"
tools:text="Dhaka" />
<RadioButton
android:id="#+id/optionB"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/_5sdp"
android:button="#drawable/custom_radio_button"
android:paddingStart="#dimen/_8sdp"
android:text="#{question.optionB}"
android:textColor="#color/black_50_percent"
tools:text="Mumbai" />
<RadioButton
android:id="#+id/optionC"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/_5sdp"
android:button="#drawable/custom_radio_button"
android:paddingStart="#dimen/_8sdp"
android:text="#{question.optionC}"
android:textColor="#color/black_50_percent"
tools:text="Islamabad" />
<RadioButton
android:id="#+id/optionD"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/_5sdp"
android:button="#drawable/custom_radio_button"
android:paddingStart="#dimen/_8sdp"
android:text="#{question.optionD}"
android:textColor="#color/black_50_percent"
tools:text="Yangun" />
</RadioGroup>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</layout>
TestFragment.kt
class TestFragment : BaseFragment() {
private val viewModel: TutorProfileViewModel by navGraphViewModels(R.id.tutor_navigation_graph)
private val questionAdapter: QuestionAdapter by lazy {
QuestionAdapter {
if (!questionAdapter.isClickable) {
context?.showToast("You can't select as time is up")
} else {
viewModel.answer[it.quesId!!] = if(it.selectedAnswer == it.correctAnswer) 1 else 0
println(viewModel.answer)
}
}
}
override fun getLayoutResId() = R.layout.fragment_test
override fun initWidget() {
question_recycler_view.adapter = questionAdapter
val quizTimer = QuizTimer(
updateCountdownText = { time_remaining_text_view.text = it },
timesUp = {
questionAdapter.isClickable = false
questionAdapter.disableRadioButton()
for (key in viewModel.answer.keys) viewModel.total += viewModel.answer[key]!!
context?.showToast("You got ${viewModel.total} out of 10")
}
)
quizTimer.startCounting()
}
}
The problem you're facing is a common problem of RecyclerView with repeating items in it showing duplicate values at the same time, as you've mentioned:
when I checked radiobutton from view item number 1 and 2, the radio button of item number 9 and 10 got selected.
You need to use setItemViewCacheSize() in your activity before setting your adapter, which is QuestionAdapter like the following, I'm giving a demo code since you didn't add your activity code:
yourRecyclerView.setItemViewCacheSize(yourDataList.size());
yourRecyclerView.setAdapter(yourQuestionAdapter);
Might be not related but i was building quiz app we had the 10 question and each question has 10sec time. So what i did taken viewpager and was refreshing ui after 10 sec and loading the next question. I would suggest you to go with viewpager it will make process smoother.
If you want you can refer this two classes :
// Fragment for viewpager
class OneToManyQuizContentFragmentKT : BaseFragment(), View.OnClickListener {
private val webListener = View.OnTouchListener { v, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
}
MotionEvent.ACTION_UP -> (v.parent as View).performClick()
else -> {
}
}
false
}
private var data: OneToManyQuestionModelKT.Questions? = null
private var listener: OneToManyQuizActivityKT? = null
private val webLongClickListener = View.OnLongClickListener { v -> true }
companion object {
fun newInstance(position: Int, data: OneToManyQuestionModelKT.Questions): OneToManyQuizContentFragmentKT {
val args = Bundle()
args.putInt("position", position)
args.putSerializable("data", data)
val fragment = OneToManyQuizContentFragmentKT()
fragment.arguments = args
return fragment
}
}
override fun getLayoutResId(): Int {
return R.layout.fragment_one_to_many_quiz_content_fregment
}
override fun initialization() {
data = arguments?.getSerializable("data") as OneToManyQuestionModelKT.Questions
}
override fun initView(v: View) {
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
//TODO REMOVE THIS AT PRODUCTION
if (data!!.right_answer.isNotEmpty()) {
txtRightAns.text = "Right answer: ${data!!.right_answer} "
}
if (data!!.difficultyLevel.isNotEmpty()) {
txtDifficultyLevel.text = "Difficulty Level: ${data!!.difficultyLevel} "
}
webQuestion.loadDataWithBaseURL(null, "Q - " + (getPosition() + 1) + " " + Utility.setHtmlFont(data!!.question), "text/html", "UTF-8", null)
webOption1.loadDataWithBaseURL(null, Utility.setHtmlFont(data?.optionOne), "text/html", "UTF-8", null)
webOption2.loadDataWithBaseURL(null, Utility.setHtmlFont(data?.optionTwo), "text/html", "UTF-8", null)
webOption3.loadDataWithBaseURL(null, Utility.setHtmlFont(data?.optionThree), "text/html", "UTF-8", null)
webOption4.loadDataWithBaseURL(null, Utility.setHtmlFont(data?.optionFour), "text/html", "UTF-8", null)
changeColor()
webViewSettings(webQuestion)
webViewSettings(webOption1)
webViewSettings(webOption2)
webViewSettings(webOption3)
webViewSettings(webOption4)
lnrOption1.setOnClickListener(this)
lnrOption2.setOnClickListener(this)
lnrOption3.setOnClickListener(this)
lnrOption4.setOnClickListener(this)
webOption1.setOnTouchListener(webListener)
webOption2.setOnTouchListener(webListener)
webOption3.setOnTouchListener(webListener)
webOption4.setOnTouchListener(webListener)
webOption1.setOnLongClickListener(webLongClickListener)
webOption2.setOnLongClickListener(webLongClickListener)
webOption3.setOnLongClickListener(webLongClickListener)
webOption4.setOnLongClickListener(webLongClickListener)
}
private fun webViewSettings(web: WebView) {
web.settings?.javaScriptEnabled = true
web.isVerticalScrollBarEnabled = false
web.isHorizontalScrollBarEnabled = false
web.webChromeClient = WebChromeClient()
web.settings?.textZoom = 90
}
override fun setupData() {
}
override fun onClick(v: View) {
when {
v == lnrOption1 -> selectOption(1)
v === lnrOption2 -> selectOption(2)
v === lnrOption3 -> selectOption(3)
v === lnrOption4 -> selectOption(4)
}
}
private fun selectOption(ans: Int) {
if (!Utility.isNetworkAvailable(activity)) {
showToast(R.string.no_internet_connection)
return
}
data!!.selected_ans = ans
val ansText = changeColor()
listener?.onAnswerSelected(data!!.selected_ans, ansText)
}
#FunctionalInterface
interface OnAnswerSelected {
fun onAnswerSelected(ans: Int, ansText: String)
}
override fun setListeners() {
}
fun setListener(listener: OneToManyQuizActivityKT): OneToManyQuizContentFragmentKT {
this.listener = listener
return this
}
private fun getPosition(): Int {
return arguments!!.getInt("position")
}
private fun changeColor(): String {
var ansText = ""
lnrOption1.setBackgroundResource(R.drawable.bg_gray_test)
lnrOption2.setBackgroundResource(R.drawable.bg_gray_test)
lnrOption3.setBackgroundResource(R.drawable.bg_gray_test)
lnrOption4.setBackgroundResource(R.drawable.bg_gray_test)
txtOptionA.setBackgroundResource(R.drawable.ans_gray_circle)
txtOptionB.setBackgroundResource(R.drawable.ans_gray_circle)
txtOptionC.setBackgroundResource(R.drawable.ans_gray_circle)
txtOptionD.setBackgroundResource(R.drawable.ans_gray_circle)
txtOptionA.setTextColor(resources.getColor(R.color.black))
txtOptionB.setTextColor(resources.getColor(R.color.black))
txtOptionC.setTextColor(resources.getColor(R.color.black))
txtOptionD.setTextColor(resources.getColor(R.color.black))
if (data != null && data?.selected_ans != 0) {
when (data!!.selected_ans) {
1 -> {
if (data!!.right_answer == Const.SELECTED_ANS_A) {
lnrOption1.setBackgroundResource(R.drawable.green_round_option_shape)
txtOptionA.setBackgroundResource(R.drawable.option_green_circle)
txtOptionA.setTextColor(resources.getColor(R.color.white))
} else {
lnrOption1.setBackgroundResource(R.drawable.red_round_option_shape)
txtOptionA.setBackgroundResource(R.drawable.option_red_circle)
txtOptionA.setTextColor(resources.getColor(R.color.white))
showRightAnswer(500)
}
ansText = data!!.optionOne
enableDisableView(lnrOption2 as View, false)
enableDisableView(lnrOption3 as View, false)
enableDisableView(lnrOption4 as View, false)
}
2 -> {
if (data!!.right_answer == Const.SELECTED_ANS_B) {
lnrOption2.setBackgroundResource(R.drawable.green_round_option_shape)
txtOptionB.setBackgroundResource(R.drawable.option_green_circle)
txtOptionB.setTextColor(resources.getColor(R.color.white))
} else {
lnrOption2.setBackgroundResource(R.drawable.red_round_option_shape)
txtOptionB.setBackgroundResource(R.drawable.option_red_circle)
txtOptionB.setTextColor(resources.getColor(R.color.white))
showRightAnswer(500)
}
ansText = data!!.optionTwo
enableDisableView(lnrOption1 as View, false)
enableDisableView(lnrOption3 as View, false)
enableDisableView(lnrOption4 as View, false)
}
3 -> {
if (data!!.right_answer == Const.SELECTED_ANS_C) {
lnrOption3.setBackgroundResource(R.drawable.green_round_option_shape)
txtOptionC.setBackgroundResource(R.drawable.option_green_circle)
txtOptionC.setTextColor(resources.getColor(R.color.white))
} else {
lnrOption3.setBackgroundResource(R.drawable.red_round_option_shape)
txtOptionC.setBackgroundResource(R.drawable.option_red_circle)
txtOptionC.setTextColor(resources.getColor(R.color.white))
showRightAnswer(500)
}
ansText = data!!.optionThree
enableDisableView(lnrOption2 as View, false)
enableDisableView(lnrOption1 as View, false)
enableDisableView(lnrOption4 as View, false)
}
4 -> {
if (data!!.right_answer == Const.SELECTED_ANS_D) {
lnrOption4.setBackgroundResource(R.drawable.green_round_option_shape)
txtOptionD.setBackgroundResource(R.drawable.option_green_circle)
txtOptionD.setTextColor(resources.getColor(R.color.white))
} else {
lnrOption4.setBackgroundResource(R.drawable.red_round_option_shape)
txtOptionD.setBackgroundResource(R.drawable.option_red_circle)
txtOptionD.setTextColor(resources.getColor(R.color.white))
showRightAnswer(500)
}
ansText = data!!.optionFour
enableDisableView(lnrOption2 as View, false)
enableDisableView(lnrOption3 as View, false)
enableDisableView(lnrOption1 as View, false)
}
else -> {
}
}
}
return ansText
}
fun showRightAnswer(duration: Long) {
val handler = Handler()
handler.postDelayed({
when (data!!.right_answer) {
Const.SELECTED_ANS_A -> {
lnrOption1.setBackgroundResource(R.drawable.green_round_option_shape)
txtOptionA.setBackgroundResource(R.drawable.option_green_circle)
txtOptionA.setTextColor(resources.getColor(R.color.white))
}
Const.SELECTED_ANS_B -> {
lnrOption2.setBackgroundResource(R.drawable.green_round_option_shape)
txtOptionB.setBackgroundResource(R.drawable.option_green_circle)
txtOptionB.setTextColor(resources.getColor(R.color.white))
}
Const.SELECTED_ANS_C -> {
lnrOption3.setBackgroundResource(R.drawable.green_round_option_shape)
txtOptionC.setBackgroundResource(R.drawable.option_green_circle)
txtOptionC.setTextColor(resources.getColor(R.color.white))
}
Const.SELECTED_ANS_D -> {
lnrOption4.setBackgroundResource(R.drawable.green_round_option_shape)
txtOptionD.setBackgroundResource(R.drawable.option_green_circle)
txtOptionD.setTextColor(resources.getColor(R.color.white))
}
}
}, duration)
}
private fun enableDisableView(view: View, enabled: Boolean) {
view.isEnabled = enabled
if (view is ViewGroup) {
for (idx in 0 until view.childCount) {
enableDisableView(view.getChildAt(idx), enabled)
}
}
}
}
// Activity where loading fragment
class OneToManyQuizActivityKT : BaseActivity(),
OneToManyQuizContentFragmentKT.OnAnswerSelected {
private val questions = ArrayList<OneToManyQuestionModelKT.Questions>()
private var pagerAdapter: TestViewPagerAdapter1? = null
private var currentQue: Int = 0
private var selectedAnsText: String? = ""
private var selectedAns = ""
private var selectedAnsPosition: Int = 0
private var rightAnsPosition: Int = 0
private var progressStatus = 0
private var isFirstTime = true
private var questionTimeCounter = Const.CHALLENGE_TIME_PER_QUESTION
private val timeForEachQuestion = Const.CHALLENGE_TIME_PER_QUESTION
private var isSkipped = false
private var oneToManyDotsAdapter: OneToManyDotsAdapterKT? = null
private var challengeId: String = ""
private var isOpponent: Boolean = false
private var initiatorId: Int = -1
private val handler = Handler()
private var challengeStatus = ""
private var wakeLock: PowerManager.WakeLock? = null
private var internetDialog: ProgressDialog? = null
private val updateRunnable = object : Runnable {
override fun run() {
runOnUiThread {
if (!Utility.isNetworkAvailable(this#OneToManyQuizActivityKT)) {
if (internetDialog != null && !internetDialog?.isShowing!!)
internetDialog?.show()
} else {
if (internetDialog != null && internetDialog?.isShowing!!)
internetDialog?.dismiss()
}
questionTimeCounter -= 1
if (selectedAns == "") {
timeProgress.progress = ++progressStatus
}
if (progressStatus == timeForEachQuestion) {
if (selectedAns.isEmpty()) {
if (oneToManyDotsAdapter != null) {
selectedAnsText = ""
questions[currentQue].question_status = Const.SKIPPED_ANSWER
oneToManyDotsAdapter?.notifyDataSetChanged()
val fragment = pagerAdapter!!.getRegisteredFragment(test_viewpager.currentItem)
(fragment as OneToManyQuizContentFragmentKT).showRightAnswer(0)
}
}
}
}
if (questionTimeCounter == 0) {
if (selectedAns.isEmpty()) {
challengeQuizSaveAnswer()
isSkipped = true
val handler = Handler()
handler.postDelayed({ nextQuestion() }, 2000)
} else {
nextQuestion()
}
} else
handler.postDelayed(this, 1000)
}
}
override fun getLayoutResId(): Int {
return R.layout.activity_one_to_many_quiz2
}
override fun initialization() {
setScreenName(getString(R.string.ga_challenge_o2m_play))
internetDialog = ProgressDialog(this)
internetDialog?.setCancelable(false)
internetDialog?.setMessage(resources.getString(R.string.waiting_internet))
timeProgress.max = Const.CHALLENGE_TIME_PER_QUESTION
recCircle.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
if (intent.hasExtra(Const.CHALLENGE_ID)) {
challengeId = intent.getStringExtra(Const.CHALLENGE_ID)
}
if (intent.hasExtra(Const.IS_OPPONENT)) {
isOpponent = intent.getBooleanExtra(Const.IS_OPPONENT, false)
}
initiatorId = if (intent.hasExtra(Const.INITIATOR_ID)) {
intent.getIntExtra(Const.INITIATOR_ID, -1)
} else {
userId
}
}
override fun initView() {
test_viewpager.overScrollMode = View.OVER_SCROLL_NEVER
test_viewpager.offscreenPageLimit = 1
test_viewpager.setPagingEnabled(false)
}
override fun setupData() {
var powerManager = getSystemService(POWER_SERVICE) as PowerManager
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"ExampleApp:Wakelock Started")
wakeLock?.acquire()
if (intent.hasExtra(Const.CHALLENGE_STATUS)) {
challengeStatus = intent.getStringExtra(Const.CHALLENGE_STATUS)
}
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
manager.cancel(Const.CHANNEL_QUIZ_ACCEPTED)
if (readPref(Const.IS_LOGIN) != Const.TRUE) {
startActivityForResult(Intent(this, MainLoginActivity::class.java).putExtra(Const.INITIATOR_ID, initiatorId), REQUEST_CODE)
} else {
setSupportActionBar(toolbar)
supportActionBar!!.title = Html.fromHtml("<font color=\"#ffffff\">" + getString(R.string.offline) + "</font>")
supportActionBar!!.setBackgroundDrawable(ColorDrawable(resources.getColor(R.color.colorPrimaryDark)))
getChallengeQuestionList()
val userdata = Gson().fromJson(readPref(Const.USERDATA), UserData::class.java)
val userName = userdata.data.first_name
txtStudentName.text = userName
}
}
override fun setListeners() {
test_viewpager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}
override fun onPageSelected(position: Int) {
currentQue = position
}
override fun onPageScrollStateChanged(state: Int) {
}
})
}
override fun onAnswerSelected(ans: Int, ansText: String) {
if (questions[currentQue].question_status != "")
return
isSkipped = false
selectedAnsText = ansText
selectedAnsPosition = ans
when (ans) {
1 -> selectedAns = Const.SELECTED_ANS_A
2 -> selectedAns = Const.SELECTED_ANS_B
3 -> selectedAns = Const.SELECTED_ANS_C
4 -> selectedAns = Const.SELECTED_ANS_D
}
when (questions[currentQue].right_answer) {
Const.SELECTED_ANS_A -> rightAnsPosition = 1
Const.SELECTED_ANS_B -> rightAnsPosition = 2
Const.SELECTED_ANS_C -> rightAnsPosition = 3
Const.SELECTED_ANS_D -> rightAnsPosition = 4
}
if (selectedAns == questions[currentQue].right_answer) {
challengeQuizSaveAnswer()
questions[currentQue].question_status = Const.RIGHT_ANSWER
oneToManyDotsAdapter?.notifyDataSetChanged()
} else {
challengeQuizSaveAnswer()
questions[currentQue].question_status = Const.WRONG_ANSWER
oneToManyDotsAdapter?.notifyDataSetChanged()
}
questionTimeCounter = 2
}
fun nextQuestion() {
timeProgress.progress = 0
selectedAns = ""
selectedAnsText = ""
selectedAnsPosition = 0
isSkipped = true
handler.removeCallbacks(updateRunnable)
questionTimeCounter = Const.CHALLENGE_TIME_PER_QUESTION
if (questions.size > currentQue + 1) {
progressStatus = 0
test_viewpager.currentItem = currentQue + 1
handler.post(updateRunnable)
} else {
if (wakeLock?.isHeld!!) {
L.e("ExampleApp", "Wakelock Released")
wakeLock?.release();
}
challengeFinishQuiz()
}
}
private fun challengeFinishQuiz() {
ChallengeMeDataProvider(this).challengeFinishQuiz(challengeId, initiatorId) { resultData ->
if (resultData.code == 1) {
startActivity(Intent(this, OneToManyThankYouActivity::class.java)
.putExtra(Const.CHALLENGE_STATUS, challengeStatus)
.putExtra("quiz_count", intent.getIntExtra("quiz_count", 0)))
Log.d(TAG, "challengeFinishQuiz() Called Sussessfully")
} else {
if (!Utility.isNetworkAvailable(this)) {
showToast(R.string.no_internet_connection)
}
Log.d(TAG, "challengeFinishQuiz() Called field")
}
}
}
private fun challengeQuizSaveAnswer() {
isFirstTime = currentQue == 0
ChallengeMeDataProvider(this).challengeQuizSaveAnswer(isOpponent, challengeId, questions[currentQue].qid, initiatorId, progressStatus, isSkipped, selectedAnsText, selectedAnsPosition, rightAnsPosition, isFirstTime) { resultData ->
if (resultData.code == 1) {
val json = JSONObject(resultData.response)
if (json.has(IS_SCORE_REVEALED)) {
val isValid = json.getBoolean(IS_SCORE_REVEALED)
if (isValid) {
showScoredRevealDialog()
}
}
Log.d(TAG, "challengeQuizSaveAnswer() Called Successfully")
}
}
}
private fun showScoredRevealDialog() {
val builder = AlertDialog.Builder(this)
builder.setMessage(getString(R.string.quiz_times_up))
builder.setPositiveButton(getString(R.string.ok)) { dialog, which ->
startActivity(Intent(this, OneToManyQuizResultActivity::class.java)
.putExtra(Const.CHALLENGE_ID, challengeId))
dialog.dismiss()
}
val alert = builder.create()
alert.show()
}
private fun getChallengeQuestionList() {
val dialog = ProgressDialog.show(this, "", getString(com.topscorerstudent.app.R.string.loading))
ChallengeMeDataProvider(this).getChallengeQuizQuestion(challengeId) { resultData ->
dismissDialog(dialog)
if (resultData.code == 1 && resultData.response != null) {
val data = Gson().fromJson(resultData.response, OneToManyQuestionModelKT::class.java)
data.data?.questions?.let {
questions.addAll(it)
}
if (data.data?.questions?.isNotEmpty()!!) {
pagerAdapter = TestViewPagerAdapter1(supportFragmentManager)
test_viewpager.adapter = pagerAdapter
handler.post(updateRunnable)
oneToManyDotsAdapter = OneToManyDotsAdapterKT(questions)
recCircle.adapter = oneToManyDotsAdapter
}
} else {
if (!Utility.isNetworkAvailable(this)) {
lntInternet.visibility = View.VISIBLE
group.visibility = View.GONE
}
}
}
}
inner class TestViewPagerAdapter1 constructor(fm: FragmentManager) : FragmentStatePagerAdapter(fm) {
private val registeredFragments: SparseArray<Fragment> = SparseArray<Fragment>()
override fun getItem(position: Int): Fragment {
return OneToManyQuizContentFragmentKT.newInstance(position, questions[position]).setListener(this#OneToManyQuizActivityKT)
}
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val fragment = super.instantiateItem(container, position) as Fragment
registeredFragments.put(position, fragment);
return fragment
}
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
registeredFragments.remove(position);
super.destroyItem(container, position, `object`)
}
fun getRegisteredFragment(position: Int): Fragment {
return registeredFragments.get(position);
}
override fun getCount(): Int {
return questions.size
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_CODE) {
setupData()
}
}
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
return if (keyCode == KeyEvent.KEYCODE_BACK) {
true
} else super.onKeyDown(keyCode, event)
}
}
I have two screens first one has recycler view list of data and searchView above it that's filter data in this recycler, the view Model code of the first fragment
class MscInspectionViewModel(val activity: LaunchActivity, val mRootView: MscInspectFragment) :
BaseViewModel(),
SwipeRefreshLayout.OnRefreshListener {
val toolBarTitle: MutableLiveData<String> = MutableLiveData()
private val getDataError = MutableLiveData<Boolean>()
var listType = MutableLiveData<Int>()
val hint = MutableLiveData<String>()
private var isRefreshing: Boolean = false
private var mSharedPreferences: SharedPreferences? = null
val dataListAdapter = ContainersUnderCheckAdapter(activity)
val backClickListener = View.OnClickListener { activity.supportFragmentManager.popBackStack() }
val filterDataByTab = object : TabLayout.OnTabSelectedListener {
override fun onTabReselected(tab: TabLayout.Tab?) {
}
override fun onTabUnselected(tab: TabLayout.Tab?) {
}
override fun onTabSelected(tab: TabLayout.Tab?) {
when (tab!!.text) {
activity.resources.getString(R.string.cidPending) -> {
listType.value = 0
getPendingData()
}
activity.resources.getString(R.string.cidDone) -> {
listType.value = 1
getDoneData()
}
}
}
}
val filterData = object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
if (query.length > 2) {
val mQuery = Utility(activity).switchArabicNumerals(query)
dataListAdapter.getFilter(3, listType.value!!).filter(mQuery)
} else {
errorMessage.value = activity.resources.getString(R.string.addCorrectNumber)
}
return true
}
override fun onQueryTextChange(newText: String): Boolean {
if (newText.length > 2) {
val mQuery = Utility(activity).switchArabicNumerals(newText)
dataListAdapter.getFilter(3, listType.value!!).filter(mQuery)
}
return false;
}
}
val closeImgListener = View.OnClickListener {
mRootView.svSearchMSC.setQuery("", true)
if (listType.value == 1) {
dataListAdapter.getFilter(1, listType.value!!).filter("ANY")
} else if (listType.value == 0) {
dataListAdapter.getFilter(2, listType.value!!).filter("PENDING")
}
}
init {
listType.value = 0
mSharedPreferences = getDefaultSharedPreferences(activity.applicationContext)
toolBarTitle.value = activity.resources.getString(R.string.mscInspectTitle)
hint.value = activity.resources.getString(R.string.msc_search)
getData()
}
fun getData() {
onRetrievePostListStart()
subscription = apiAccount.getContainersUnderCheck(
"getContainersUnderCheck",
mSharedPreferences!!.getString(Constants.CFID, "")!!,
mSharedPreferences!!.getString(Constants.CFTOKEN, "")!!
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe {}
.doOnTerminate {}
.subscribe({ result ->
result?.let {
if (result.ResponseCode != null && result.ResponseCode.trim() != "000") {
onRetrievePostListError(result.ResponseMessage)
} else {
result.ContainersData?.let { it1 -> onRetrievePostListSuccess(it1) }
}
}
}, { throwable ->
android.util.Log.e("getDataInquiry", throwable.message!!)
onRetrievePostListError(activity.resources.getString(R.string.general_error))
})
}
private fun getPendingData() {
val query = mRootView.svSearchMSC.query.toString()
if (query == "") {
dataListAdapter.getFilter(2, listType.value!!).filter("PENDING")
} else {
if (query.length > 2) {
dataListAdapter.getFilter(3, listType.value!!).filter(query)
} else {
errorMessage.value = activity.resources.getString(R.string.addCorrectNumber)
}
}
}
private fun getDoneData() {
val query = mRootView.svSearchMSC.query.toString()
if (query == "") {
dataListAdapter.getFilter(1, listType.value!!).filter("ANY")
} else {
if (query.length > 2) {
dataListAdapter.getFilter(3, listType.value!!).filter(query)
} else {
errorMessage.value = activity.resources.getString(R.string.addCorrectNumber)
}
}
}
private fun onRetrievePostListStart() {
loading.value = true
}
private fun onRetrievePostListFinish() {
loading.value = false
isRefreshing = false
}
private fun onRetrievePostListSuccess(containersData: List<ContainersData>) {
onRetrievePostListFinish()
dataListAdapter.updateInquiryAdapter(containersData as ArrayList<ContainersData>)
if (listType.value == 1) {
dataListAdapter.getFilter(1, listType.value!!).filter("ANY")
} else if (listType.value == 0) {
dataListAdapter.getFilter(2, listType.value!!).filter("PENDING")
}
}
private fun onRetrievePostListError(message: String?) {
onRetrievePostListFinish()
getDataError.value = true
errorMessage.value = message
}
override fun onCleared() {
super.onCleared()
subscription.dispose()
}
override fun onRefresh() {
isRefreshing = true
getData()
}
}
adapter is :
class ContainersUnderCheckAdapter(val activity: LaunchActivity) :
RecyclerView.Adapter<ContainersUnderCheckAdapter.ViewHolder>() {
private lateinit var mDataSet: ArrayList<ContainersData>
private lateinit var mDataSetFiltered: ArrayList<ContainersData>
fun updateInquiryAdapter(dataSet: ArrayList<ContainersData>) {
mDataSet = ArrayList()
mDataSet.clear()
mDataSet.addAll(dataSet)
mDataSetFiltered = mDataSet
getFilter(2, 1).filter("PENDING")
// notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding: ContianerItemFieldLayoutBinding = DataBindingUtil
.inflate(
LayoutInflater.from(parent.context),
R.layout.contianer_item_field_layout,
parent,
false
)
return ViewHolder(binding, activity)
}
override fun getItemCount(): Int {
return if (::mDataSetFiltered.isInitialized) mDataSetFiltered.size else 0
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(mDataSetFiltered[position])
}
operator fun get(position: Int): ContainersData {
return mDataSetFiltered.get(position)
}
/**
* #filterType :
* IF 1 : filter on Data Type RJCTD + APPROVED
* 2 : filter on Data Type PENDING
* 3 :
*/
fun getFilter(filterType: Int, listType: Int): Filter {
return object : Filter() {
override fun performFiltering(charSequence: CharSequence): FilterResults {
val charString = charSequence.toString()
mDataSetFiltered = if (charString.isEmpty()) {
mDataSet
} else {
val filteredList = ArrayList<ContainersData>()
for (row in mDataSet) {
when (filterType) {
1 -> {
if (row.status == "RJCTD" || row.status == "APPROVED") {
filteredList.add(row)
}
}
2 -> {
if (row.status == charString) {
filteredList.add(row)
}
}
3 -> {
when (listType) {
0 -> {
if ((row.CID!!.contains(charString.toUpperCase(Locale.ROOT)) || row.TN!!.contains(
charSequence
) || row.PN!!.contains(charSequence)) && row.status == "PENDING"
) {
filteredList.add(row)
}
}
1 -> {
if ((row.CID!!.contains(charString.toUpperCase(Locale.ROOT)) || row.TN!!.contains(
charSequence
) || row.PN!!.contains(charSequence)) && row.status != "PENDING"
) {
filteredList.add(row)
}
}
}
}
}
}
filteredList
}
val filterResults = FilterResults()
filterResults.values = mDataSetFiltered
return filterResults
}
override fun publishResults(
charSequence: CharSequence,
filterResults: FilterResults
) {
if (::mDataSetFiltered.isInitialized) {
mDataSetFiltered = try {
filterResults.values as ArrayList<ContainersData>
} catch (e: Exception) {
Log.e("mDataSetFiltered",e.message!!)
ArrayList()
}
when (filterType) {
1->{
mDataSetFiltered.sortWith(Comparator { p0, p1 -> p1!!.UpdateDate.compareTo(p0!!.UpdateDate) })
}
2->{
mDataSetFiltered.sortWith(Comparator { p0, p1 -> p0!!.ID!!.compareTo(p1.ID!!) })
}
}
}
// refresh the list with filtered data
notifyDataSetChanged()
}
}
}
class ViewHolder(
private val binding: ContianerItemFieldLayoutBinding,
val activity: LaunchActivity
) : RecyclerView.ViewHolder(binding.root) {
private val viewModel = MscInspectionListViewModel(activity)
fun bind(data: ContainersData) {
viewModel.bind(data)
binding.viewModel = viewModel
}
}
}
any data in this recycler on click go to fragment has tow recycler first one to show data, the second one to pick Images
the second-page code
class MSCDataFragment : Fragment() {
lateinit var rootView: View
lateinit var activity: LaunchActivity
lateinit var utility: Utility
lateinit var loadingView: LoadingView
private lateinit var viewModel: MSCDataViewModel
private lateinit var binding: FragmentMscdataBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (getActivity() != null) {
activity = getActivity() as LaunchActivity
utility = Utility(activity)
loadingView = LoadingView(activity)
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_mscdata, container, false)
rootView = binding.root
initial()
return rootView
}
private fun initial() {
viewModel = ViewModelProvider(
this, ViewModelFactory(
activity,
arguments!!.getSerializable("Data") as ContainersData
)
).get(MSCDataViewModel::class.java)
binding.viewModel = viewModel
// binding.imgList.layoutManager = GridLayoutManager(activity, 3)
binding.containerInfo.layoutManager = LinearLayoutManager(activity)
binding.openCIDNotValid.typeface =
Typeface.createFromAsset(activity.assets, "Bahij_Janna-Regular.ttf")
binding.openCIDNotValid.setOnCheckedChangeListener(viewModel.onOpenCidNotValidListener)
viewModel.loading.observe(this, Observer { loading ->
loading?.let {
if (it) {
loadingView.show()
} else {
loadingView.dismiss()
}
}
})
viewModel.errorMessage.observe(this, Observer { msg ->
msg?.let {
utility.ShowToast(msg)
}
})
viewModel.imagesAdapters2.observe(this, Observer { msg ->
msg?.let {
binding.imgList.apply {
layoutManager = GridLayoutManager(activity, 3)
adapter = it
}
}
})
rootView.toolbar_Back.setOnClickListener(viewModel.backClickListener)
binding.btnAddImages.setOnClickListener(viewModel.pickImages)
binding.successContianer.setOnClickListener(viewModel.correctContainer)
binding.damagedContianer.setOnClickListener(viewModel.wrongContainer)
}
}
the view model is :
class MSCDataViewModel(val activity: LaunchActivity, val containersData: ContainersData) :
BaseViewModel(), GetImagesListener {
#Inject
lateinit var restApiAccount: RestApiAccount
val toolBarTitle: MutableLiveData<String> = MutableLiveData()
val ButtonText: MutableLiveData<String> = MutableLiveData()
var openCIDNotValidVisibility = MutableLiveData<Int>()
private val getDataError = MutableLiveData<Boolean>()
val btnImagesVisibility = MutableLiveData<Int>()
var imgeNoteVisibility = MutableLiveData<Int>()
var successVisibility = MutableLiveData<Int>()
var damagedVisibility = MutableLiveData<Int>()
var notesVisibility = MutableLiveData<Int>()
val btnVisibility = MutableLiveData<Int>()
var canNotOpen = MutableLiveData<Int>()
private val images = ArrayList<Image>()
var utility = Utility(activity)
private var CURRENTINDEX = 0
private var mSharedPreferences: SharedPreferences? = null
val DataListAdapter = ContainerDataAdapter(activity)
var imagesAdapter = ContainerImagesAdapter(activity, containersData.status!!, ArrayList())
val imagesAdapters2 = MutableLiveData<ContainerImagesAdapter2>()
val userInfo: UserInfo
val backClickListener = View.OnClickListener { activity.supportFragmentManager.popBackStack() }
val pickImages = View.OnClickListener {
pickImages()
}
val correctContainer = View.OnClickListener {}
val onOpenCidNotValidListener =
CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
if (isChecked) {
successVisibility.value = View.GONE
canNotOpen.value = 1
} else {
canNotOpen.value = 0
successVisibility.value = View.VISIBLE
}
}
val wrongContainer = View.OnClickListener {}
var mscNotes: ObservableField<String> = ObservableField("")
init {
canNotOpen.value = 0
mSharedPreferences =
PreferenceManager.getDefaultSharedPreferences(activity.applicationContext)
toolBarTitle.value = containersData.CID
ButtonText.value = activity.resources.getString(R.string.cleanContianer)
userInfo = utility.readObjectFromSharedPreferences(
mSharedPreferences,
Constants.USER_INFO_KEY,
UserInfo::class.java
) as UserInfo
openCIDNotValidVisibility.value = View.GONE
fillData()
}
private fun fillData() {
val data: LinkedHashMap<String, String> = containersData.data!!
val captionsMap = utility.readObjectFromSharedPreferences(
mSharedPreferences, Constants.CAPTIONS_MAP_KEY,
HashMap::class.java
) as HashMap<String, String>
if (containersData.data.size > 0) {
val list = ArrayList<KeyValueModel>()
for (inside in data.keys) {
val ky = captionsMap[inside]
val value = data[inside].toString()
ky?.let { KeyValueModel(it, value) }?.let { list.add(it) }
}
DataListAdapter.updateInquiryAdapter(list)
} else {
errorMessage.value = activity.resources.getString(R.string.no_data)
}
if (containersData.ImageList != null && containersData.ImageList.isNotEmpty()) {
imagesAdapter.updateContainerImagesAdapter(containersData.ImageList)
}
}
private fun pickImages() {
activity.setCallBack(this)
val pictureDialog: AlertDialog
val builder = activity.let { AlertDialog.Builder(it) }
val dialogView = View.inflate(activity, R.layout.choose_camera_method, null)
builder.setView(dialogView)
val nafithPopupContainer = dialogView.findViewById<RelativeLayout>(R.id.RLTitle)
nafithPopupContainer.setBackgroundColor(
ContextCompat.getColor(
activity,
R.color.mainColor
)
)
val popUpGallery = dialogView.findViewById<LinearLayout>(R.id.PopupGellary)
val popUpCamera = dialogView.findViewById<LinearLayout>(R.id.PopupCamera)
pictureDialog = builder.create()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Objects.requireNonNull<Window>(pictureDialog.window)
.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
} else {
if (pictureDialog.window != null) {
pictureDialog.window!!.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
}
}
popUpGallery.setOnClickListener {
fromGallery()
pictureDialog.dismiss()
}
popUpCamera.setOnClickListener {
fromCamera()
pictureDialog.dismiss()
}
val popupClose = dialogView.findViewById<ImageView>(R.id.popupClose)
popupClose.setOnClickListener { pictureDialog.dismiss() }
pictureDialog.show()
}
private fun fromGallery() {
ImagePicker.create(activity)
.toolbarImageTitle(activity.resources.getString(R.string.get_image))
.toolbarArrowColor(ContextCompat.getColor(activity, R.color.colorWhite))
.showCamera(false)
.limit(6)
.start()
}
private fun fromCamera() {
ImagePicker.cameraOnly().start(activity)
}
override fun onGetImage(image: Image) {
imgeNoteVisibility.value = View.GONE
imagesAdapter.updateContainerImagesAdapter(image)
images.add(image)
}
override fun addingImagesDone(mImages: MutableList<Image>) {
images.clear()
images.addAll(mImages)
imgeNoteVisibility.value = View.GONE
val listString :ArrayList<String> = ArrayList()
for (i in mImages.indices){
listString.add(mImages[i].path)
}
imagesAdapters2.value = ContainerImagesAdapter2(activity,containersData.status!!,listString)
imagesAdapters2.value!!.notifyItemRangeChanged(0,listString.size)
}
override fun onImgDelete(image: String) {
var x = 0
try {
for (i in 0 until images.size) {
x = i
if (images[i].path == image) {
images.remove(images[i])
}
}
} catch (e: Exception) {
Log.e("errorImages", e.message!!)
Log.e("xx", x.toString())
}
}
private fun onRetrievePostListStart() {
loading.value = true
}
private fun onRetrievePostListFinish() {
loading.value = false
}
private fun onRetrievePostListSuccess(msg: String?) {
onRetrievePostListFinish()
}
private fun onRetrievePostListError(message: String?) {
onRetrievePostListFinish()
getDataError.value = true
errorMessage.value = message
}
}
Adapter code is :
class ContainerImagesAdapter2() : RecyclerView.Adapter<ContainerImagesAdapter2.ViewHolder>() {
var status: String = ""
lateinit var activity: LaunchActivity
lateinit var utility: Utility
constructor(
mActivity: LaunchActivity,
mStatus: String,
pathsList: ArrayList<String>
) : this() {
activity = mActivity
pathsDataSet = pathsList
status = mStatus
utility = Utility(activity)
}
private var pathsDataSet: ArrayList<String> = ArrayList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding: ContianerImageFieldBinding = DataBindingUtil
.inflate(
LayoutInflater.from(parent.context),
R.layout.contianer_image_field,
parent,
false
)
return ViewHolder(binding, activity)
}
override fun getItemCount(): Int {
return pathsDataSet.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bindPath(pathsDataSet[position], position)
}
inner class ViewHolder(
private val binding: ContianerImageFieldBinding,
val activity: LaunchActivity
) : RecyclerView.ViewHolder(binding.root) {
private val viewModel = MscImagesListViewModel(activity)
fun bindPath(data: String, position: Int) {
viewModel.bindPath(data)
binding.viewModel = viewModel
if (status != "PENDING") {
binding.closeImg.visibility = View.GONE
}
binding.closeImg.setOnClickListener {}
binding.mainImg.setOnClickListener {
val fragment = FullImageFragment()
val bundle = Bundle()
val list = ArrayList<String>()
for (item in 0 until pathsDataSet.size) {
list.add(pathsDataSet[item])
}
bundle.putSerializable("ImageList", list)
bundle.putInt("Position", position)
fragment.arguments = bundle
activity.supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, fragment).addToBackStack(fragment.tag)
.commit()
}
}
}
}
if you filter data using search view in the first-page and pick images in the second page , list of picked images doesn't appear, if you going to the second page without filtering data everything ok
solve Problem found
Just Update constraint-layout library in gradle dependencies to version '2.0.0-beta4'
I'm tying to parse JSON in recyclerview. App compiles fine but it's outputting empty/blank screen
BlogAdapter.kt
class BlogAdapter(private val blogList: List<Blog>) : RecyclerView.Adapter<BlogAdapter.ViewHolder>() {
override fun getItemCount()= blogList.size
private var mContext: Context? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
this.mContext=parent.context;
return ViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.character_item,
parent,
false
)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val mBlog = this.blogList[position]
if (mBlog.img != null) {
Glide.with(mContext!!)
.load(mBlog.img)
.into(holder.ivThumbnail)
}
if (mBlog.name != null) {
holder.tvTitle.text = mBlog.name
println("Log: Kabe "+mBlog.name)
}
}
class ViewHolder(itemView:View):RecyclerView.ViewHolder(itemView){
val ivThumbnail:ImageView = itemView.findViewById(R.id.ivThumbnail);
val tvTitle:TextView = itemView.findViewById(R.id.tvTitle);
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
var mainViewModel: MainViewModel? = null
var mBlogAdapter: BlogAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
getPopularBlog()
swipe_refresh.setOnRefreshListener { getPopularBlog() }
}
private fun getPopularBlog() {
swipe_refresh.isRefreshing = false
mainViewModel!!.allBlog.observe(this, Observer { charactersList ->
prepareRecyclerView(charactersList)
})
}
private fun prepareRecyclerView(blogList: List<Blog>) {
mBlogAdapter = BlogAdapter(blogList)
if (this.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
blogRecyclerView.layoutManager = LinearLayoutManager(this)
} else {
blogRecyclerView.layoutManager = GridLayoutManager(this, 4)
}
blogRecyclerView.itemAnimator = DefaultItemAnimator()
blogRecyclerView.adapter = mBlogAdapter
}
}
My Json file looks like this:
[
{
"id": 1,
"name": "potter",
"img": "https://images.example.com/potter.jpg"
},
{ …}
]
I've created it based on this tutorial: https://itnext.io/kotlin-wrapping-your-head-around-livedata-mutablelivedata-coroutine-networking-and-viewmodel-b552c3a74eec
Any suggestions please
EDIT:
class BlogRepository() {
private var character = mutableListOf<ABCCharacters>()
private var mutableLiveData = MutableLiveData<List<ABCCharacters>>()
val completableJob = Job()
private val coroutineScope = CoroutineScope(Dispatchers.IO + completableJob)
private val thisApiCorService by lazy {
RestApiService.createCorService()
}
fun getMutableLiveData():MutableLiveData<List<ABCCharacters>> {
coroutineScope.launch {
val request = thisApiCorService.getPopularBlog()
withContext(Dispatchers.Main) {
try {
val response = request.await()
val mBlogWrapper = response;
if (/*mBlogWrapper != null &&*/ mBlogWrapper.isNotEmpty()) {
character = mBlogWrapper as MutableList<ABCCharacters>
mutableLiveData.value = character
}
} catch (e: HttpException) {
// Log exception //
} catch (e: Throwable) {
// Log error //)
}
}
}
return mutableLiveData;
}
}
You forget to call notifyDataSetChanged, when you setup your RecyclerView widget. Below the full method call, to make it works.
private fun prepareRecyclerView(blogList: List<Blog>) {
mBlogAdapter = BlogAdapter(blogList)
if (this.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
blogRecyclerView.layoutManager = LinearLayoutManager(this)
} else {
blogRecyclerView.layoutManager = GridLayoutManager(this, 4)
}
blogRecyclerView.itemAnimator = DefaultItemAnimator()
blogRecyclerView.adapter = mBlogAdapter
mBlogAdapter.notifyDataSetChanged()
}
Try using below implementation:
class MainActivity : AppCompatActivity() {
lateinit var mainViewModel: MainViewModel
var mBlogAdapter: BlogAdapter? = null
var blogList: List<Blog> = arrayListOf()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
// init your RV here
prepareRecyclerView()
getPopularBlog()
swipe_refresh.setOnRefreshListener { mainViewModel.getAllBlog() }
}
private fun getPopularBlog() {
swipe_refresh.isRefreshing = false
mainViewModel.getAllBlog().observe(this, Observer { charactersList ->
blogList = charactersList
mBlogAdapter?.notifyDataSetChanged()
})
}
private fun prepareRecyclerView() {
mBlogAdapter = BlogAdapter(blogList)
if (this.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
blogRecyclerView.layoutManager = LinearLayoutManager(this)
} else {
blogRecyclerView.layoutManager = GridLayoutManager(this, 4)
}
blogRecyclerView.itemAnimator = DefaultItemAnimator()
blogRecyclerView.adapter = mBlogAdapter
}
}
Modify your view model like below:
class MainViewModel() : ViewModel() {
val characterRepository= BlogRepository()
fun getAllBlog(): MutableLiveData<List<ABCCharacters>> {
return characterRepository.getMutableLiveData()
}
override fun onCleared() {
super.onCleared()
characterRepository.completableJob.cancel()
}
}
I have a list of user record sound
The user can click on the list and play the audio file
How can I handle the MediaPlayer and the RecyclerView item in the Seekbar as well in the MVVM correctly?
That is, when the user clicks, the visitor changes the item and updates itself, and when it is clicked on an item again, it updates itself.
I did it now, but unfortunately, to the dirtiest possible form
activity code :
class SoundListActivity : BaseActivity(), Observer<List<VoiceEntity>>, VoiceAdapter.OnClickItemListener,
OnMultiSelectVoiceListener {
private lateinit var viewModel: VoiceViewModel
private val adapter = VoiceAdapter()
private val player = MediaPlayer()
private var positionPlayItem = -1
companion object {
fun start(context: Context) {
context.startActivity(Intent(context, SoundListActivity::class.java))
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sound_list)
viewModel = ViewModelProviders.of(this).get(VoiceViewModel::class.java)
viewModel.mutableList!!.observe(this, this)
adapter.onItemClickListener = this
adapter.listenerMultiSelect = this
recycler.layoutManager = LinearLayoutManager(this)
recycler.adapter = adapter
fabAdd.setOnClickListener {
stopPlay()
RecordSoundActivity.start(this)
}
toolbar.setIconLeftListener(View.OnClickListener {
stopPlay()
finish()
})
}
override fun onChanged(list: List<VoiceEntity>?) {
this.progressBar.visibility = View.GONE
this.layoutEmptyState.visibility = View.INVISIBLE
this.adapter.removeAll()
if (list == null || list.isEmpty()) {
layoutEmptyState.visibility = View.VISIBLE
return
}
adapter.addItems(ArrayList(list))
}
override fun onClickItem(item: VoiceEntity, position: Int) {
if (player.isPlaying) {
player.reset()
}
if (item.isPlaying) {
item.isPlaying = false
player.reset()
adapter.notifyDataSetChanged()
return
}
this.positionPlayItem = position
adapter.items!!.forEach {
if (it != item) {
it.isPlaying = false
}
}
player.setDataSource(item.path)
player.prepare()
player.start()
item.isPlaying = true
adapter.notifyDataSetChanged()
player.setOnCompletionListener {
player.reset()
adapter.notifyItemChanged(position)
item.isPlaying = false
}
}
private fun stopPlay() {
if (positionPlayItem == -1) {
return
}
player.reset()
adapter.items!![positionPlayItem].isPlaying = false
adapter.notifyItemChanged(positionPlayItem)
}
override fun onMultiSelectVoice(items: ArrayList<VoiceEntity>) {
stopPlay()
if (items.size == 0) {
layoutSelectItem.visibility = View.GONE
return
}
txtCounterSelect.text = String.format(getString(R.string.selected_number), items.size.toString())
setStatusBarColor(R.color.black)
if (layoutSelectItem.visibility == View.GONE) {
layoutSelectItem.visibility = View.VISIBLE
}
if (items.size > 1) {
imgShare.visibility = View.GONE
imgEdit.visibility = View.GONE
} else {
imgShare.visibility = View.VISIBLE
imgEdit.visibility = View.VISIBLE
}
imgCancelSelect.setOnClickListener {
resetData()
}
imgEdit.setOnClickListener {
edit(items.first())
}
imgShare.setOnClickListener {
if (items.isEmpty()) {
return#setOnClickListener
}
shareVoice(this, items[0].path)
}
imgDelete.setOnClickListener {
val alertDialog = AlertDialog.Builder(
supportFragmentManager,
getString(R.string.note), getString(R.string.do_you_sure_delete)
)
alertDialog.setBtnNegative(getString(R.string.no), View.OnClickListener {
alertDialog.dialog!!.dismiss()
})
alertDialog.setBtnPositive(getString(R.string.yes), View.OnClickListener {
val ex = Executors.newSingleThreadExecutor()
items.forEach { item ->
viewModel.remove(item)
ex.execute { File(item.path).deleteOnExit() }
}
items.clear()
layoutSelectItem.visibility = View.GONE
setStatusBarColor(R.color.colorPrimaryDark)
alertDialog.dialog!!.dismissAllowingStateLoss()
})
alertDialog.build().show()
}
}
private fun resetData() {
adapter.itemsSelected.clear()
adapter.items?.forEach { item ->
item.statusSelect = false
}
adapter.notifyDataSetChanged()
layoutSelectItem.visibility = View.GONE
setStatusBarColor(R.color.colorPrimaryDark)
}
private fun edit(item: VoiceEntity) {
val bottomSheet = NameBottomSheet(supportFragmentManager)
bottomSheet.listener = object : NameBottomSheet.OnTitleListener {
override fun onTitle(title: String) {
item.title = title
viewModel.update(item)
resetData()
}
}
bottomSheet.item = item
bottomSheet.show()
}
override fun onBackPressed() {
if (layoutSelectItem.visibility == View.VISIBLE) {
resetData()
return
}
stopPlay()
super.onBackPressed()
}
}
adapter class code :
class VoiceAdapter : AdapterRecyclerView<VoiceEntity>() {
var onItemClickListener: OnClickItemListener? = null
var itemsSelected: ArrayList<VoiceEntity> = ArrayList()
var listenerMultiSelect: OnMultiSelectVoiceListener? = null
override fun getItemLayout(viewType: Int): Int {
return R.layout.item_voice
}
override fun onBindView(
viewDataBinding: ViewDataBinding,
viewHolder: ItemViewHolder,
position: Int,
viewType: Int,
element: VoiceEntity
) {
val binding = viewDataBinding as ItemVoiceBinding
binding.txtTitle.text = element.title
binding.txtDate.text = element.date.toAgoTime(context!!)
binding.icPlay.setImageResource(if (element.isPlaying) R.drawable.ic_pause else R.drawable.ic_play)
binding.seekBar.max = element.duration / 60
val colorSelectItem =
ContextCompat.getColor(binding.rootLayout.context, R.color.color_background_select_item_recycler_view)
val color = if (element.statusSelect) colorSelectItem else Color.TRANSPARENT
binding.rootLayout.setBackgroundColor(color)
if (element.statusSelect) {
changeColorLight(binding)
} else {
changeColorDarker(binding)
}
if (element.isPlaying) {
binding.layoutPlaying.visibility = View.VISIBLE
binding.lottieLayer.playAnimation()
//TODO : change handled voice progressBar show
val t = object : Thread() {
override fun run() {
super.run()
for (i in 0..element.duration) {
Thread.sleep(60)
binding.seekBar.progress = i
if (!element.isPlaying) break
}
}
}
t.start()
} else {
binding.layoutPlaying.visibility = View.GONE
binding.lottieLayer.cancelAnimation()
}
binding.rootLayout.setOnClickListener {
if (itemsSelected.size > 0) {
val item = items!![viewHolder.adapterPosition]
if (itemsSelected.contains(item)) {
item.statusSelect = false
itemsSelected.remove(item)
binding.rootLayout.animatedColorBackgroundSelected(false)
listenerMultiSelect?.onMultiSelectVoice(itemsSelected)
changeColorDarker(binding)
return#setOnClickListener
}
item.statusSelect = true
itemsSelected.add(item)
binding.rootLayout.animatedColorBackgroundSelected()
listenerMultiSelect?.onMultiSelectVoice(itemsSelected)
changeColorLight(binding)
return#setOnClickListener
}
onItemClickListener?.onClickItem(element, position)!!
}
binding.rootLayout.setOnLongClickListener {
val item = items!![viewHolder.adapterPosition]
if (itemsSelected.contains(item)) {
item.statusSelect = false
itemsSelected.remove(item)
binding.rootLayout.animatedColorBackgroundSelected(false)
changeColorDarker(binding)
listenerMultiSelect?.onMultiSelectVoice(itemsSelected)
}
item.statusSelect = true
itemsSelected.add(item)
binding.rootLayout.animatedColorBackgroundSelected()
changeColorLight(binding)
listenerMultiSelect?.onMultiSelectVoice(itemsSelected)
true
}
}
private fun changeColorLight(binding: ItemVoiceBinding) {
binding.txtDate.setTextColor(ContextCompat.getColor(binding.root.context, R.color.color_subtitle_light))
binding.txtTitle.setTextColor(ContextCompat.getColor(binding.root.context, R.color.color_title_light))
}
private fun changeColorDarker(binding: ItemVoiceBinding) {
binding.txtDate.setTextColor(ContextCompat.getColor(binding.root.context, R.color.color_subtitle))
binding.txtTitle.setTextColor(ContextCompat.getColor(binding.root.context, R.color.color_title))
}
interface OnClickItemListener {
fun onClickItem(item: VoiceEntity, position: Int)
}
}
github repository (open source project)