everyone! I am new in Android Develop, can you please help me?
I have a big problem! I cant do normal delete my selected items from recyclerview and API.
I am trying do multiselect delete dialogs in my APP from Menu btn, but i cant do this, because logic in my APP do some hard architecture. I need know "index" for deleting dialog...
P.S. one index = one Room
P.S.S. Sorry for my bad eng...
I have -fun- for delete my dialog:
override fun deleteRoom(index: Int,token: String,email: String,idRoom: String,idProject: String) { roomsIterator.startDeleteRoom(index, token, email, idRoom, idProject) }
override fun startDeleteRoom(index: Int,token: String,email: String,idRoom: String,idProject: String ) { val call = requestServer.deleteDialog(idProject, idRoom, token, email)
call.enqueue(object : Callback<Request> {
override fun onResponse(call: Call<Request>?, response: Response<Request>?) {
response?.let {
if (response.isSuccessful && it.body() != null) {
presenterListener?.onSuccessDeleteRoom(index)
} else {
try {
val jObjError = JSONObject(it.errorBody()!!.string())
presenterListener?.onFailedDeleteRoom(jObjError.getString("error"))
} catch (e: JSONException) {
presenterListener?.onFailedDeleteRoom("unknown error")
}
}
}
}
override fun onFailure(call: Call<Request>?, t: Throwable?) {
t.processingFailure(presenterListener)
}
})
}
Function for delete dialog :
fun deleteItem(index: Int) {
roomsPresenterImpl.deleteRoom(index,token,login,roomsAdapter.roomsList[index].id,idProject)}
I have Adapter code with multiselect recyclerview:
class RoomsAdapter(var roomsList: MutableList<Room>,private val roomsAdapterListener: RoomsAdapterListener?,private val showMenuDelete: (Boolean) -> Unit,) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var isEnable = false
private val itemSelectedList = mutableListOf<Int>()
override fun getItemViewType(position: Int) = if (position == roomsList.size)
RoomsType.LOAD_MORE.ordinal else RoomsType.ITEM.ordinal
override fun onBindViewHolder(h: RecyclerView.ViewHolder, position: Int) {
when (h) {
is RoomsViewHolder -> {
val item = roomsList[position]
h.iv.visibility = View.GONE
h.itemView.setOnLongClickListener {
selectedItem(h, item, position)
true
}
h.itemView.setOnClickListener {
if (itemSelectedList.contains(position)) {
itemSelectedList.remove(position)
h.iv.visibility = View.GONE
item.selected = false
if (itemSelectedList.isEmpty()) {
showMenuDelete(false)
isEnable = false
}
} else if (isEnable) {
selectedItem(h, item, position)
} else {
roomsAdapterListener?.onItemClick(roomsList[position])
}
if (roomsList[position].last_message != null) {
h.itemView.dateCreateView.text = SimpleDateFormat(
"dd MMMM yyyy HH:mm",
Locale.getDefault()
).format(roomsList[position].updated_at)
h.itemView.nameView.text = roomsList[position].name
if (roomsList[position].last_message?.text.isNullOrEmpty()) {
h.itemView.lastMessageView.text =
h.itemView.context.getString(R.string.attach_messages)
} else {
h.itemView.lastMessageView.text = roomsList[position].last_message?.text
}
} else {
h.itemView.myMessageView.visibility = View.GONE
h.itemView.dateCreateView.text = SimpleDateFormat(
"dd MMMM yyyy",
Locale.getDefault()
).format(roomsList[position].created_at)
h.itemView.nameView.text = roomsList[position].name
h.itemView.lastMessageView.text =
h.itemView.context.getString(R.string.no_messages)
}
Glide.with(h.itemView.imageView).clear(h.itemView.imageView)
Glide
.with(h.itemView.imageView)
.load(roomsList[position].avatar)
.error(R.drawable.ic_no_avatar)
.dontAnimate()
.into(h.itemView.imageView)
h.itemView.myMessageView.visibility =
if (roomsList[position].last_message != null && !roomsList[position]
.last_message!!
.client_replica!!
) {
h.itemView.llbgText.setBackgroundResource(R.drawable.bg_select)
View.VISIBLE
} else {
h.itemView.llbgText.background = null
View.GONE
}
}
is LoadMoreViewHolder -> {
roomsAdapterListener?.onLoadmore()
}
}
THIS FUN FRO VISUAL DELETING with out API
fun deleteSelectedItem() {if (itemSelectedList.isNotEmpty()) {roomsList.removeAll { item -> item.selected }isEnable = falseitemSelectedList.clear() }
notifyDataSetChanged()
}
fun clearAll() {roomsList.clear()notifyDataSetChanged()}
fun update(rl: MutableList<Room>) {
roomsList.clear()
roomsList.addAll(rl)
notifyDataSetChanged()
}
fun delete(index: Int) {
roomsList.removeAt(index)
notifyItemRemoved(index)
}
fun loadMore(lm: Boolean) {
loadmore = lm
notifyDataSetChanged()
}
Related
the app have a chatting function so I used 'stackFromEnd' method of recyclerview to show a last item of list firstly like other chatting app however, it not worked. it stopped in the middle of placing message items.
MessageActivity OnCreate
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_message)
binding.lifecycleOwner = this
val intent = intent
room = intent.getSerializableExtra("room") as RoomEntity
roomUid = room!!.uid
other = intent.getSerializableExtra("other") as RoomMemberEntity
ownUid = AppContext.uid
layoutManager = LinearLayoutManager(this)
layoutManager.stackFromEnd = true
layoutManager.isSmoothScrollbarEnabled = true
binding.recyclerMessages.recycledViewPool.setMaxRecycledViews(0,0)
binding.recyclerMessages.layoutManager = layoutManager
adapter = MessageAdapter(messageList, ownUid, other)
binding.recyclerMessages.adapter = adapter
binding.buttonSend.setOnClickListener {
val text = binding.editMessage.text.toString()
if (text.length > 0) {
binding.editMessage.text!!.clear()
lifecycleScope.launch(Dispatchers.IO) {
val sendResponse = viewModel.sendMessage(text, roomUid)
when(sendResponse) {
is Response.Error -> {
withContext(Dispatchers.Main) {
Toast.makeText(this#MessageActivity,"message not sent due to the internet connection error.",Toast.LENGTH_SHORT)
}
}
else -> {
}
}
}
}
}
binding.editMessage.doAfterTextChanged { text ->
if (text!!.length > 0)
binding.buttonSend.visibility = View.VISIBLE
else
binding.buttonSend.visibility = View.GONE
}
}
MessageActivity OnStart
lifecycleScope.launch(Dispatchers.IO) {
viewModel.fetchMessage(roomUid).collect { fetchResponse->
when (fetchResponse) {
is Response.Success -> {
val map = fetchResponse.data
val type = map.keys.first()
val message = map.get(type)
if (message != null) {
if (messageList.contains(message)) {
val index = messageList.indexOf(message)
messageList.set(index, message)
} else {
messageList.add(message)
}
if (type == ADDED) {
if (message.read == false && !message.sender.equals(ownUid)) {
val readResponse = viewModel.readMessage(roomUid, message.uid)
when(readResponse) {
is Response.Error -> {
}
else -> {
}
}
}
}
withContext(Dispatchers.Main) {
adapter.changeMessages(messageList)
adapter.notifyDataSetChanged()
}
} else {
}
}
is Response.No -> {
}
is Response.Error -> {
}
else -> {
}
}
}
}
ViewModel
private fun _fetchMessage (roomUid : String) : Flow<Response<Map<Int, MessageEntity>>> {
val flow = repository.fetchMessage(roomUid).shareIn(viewModelScope, SharingStarted.WhileSubscribed(), 0)
return flow
}
fun fetchMessage (roomUid: String) : Flow<Response<Map<Int, MessageEntity>>> {
return _fetchMessage(roomUid)
}
DataSourceImpl
val reference =
databaseReference.child("messages").child(roomUid)
val subscription =
reference.addChildEventListener(object : ChildEventListener {
override fun onChildAdded(
snapshot: DataSnapshot,
previousChildName: String?
) {
val message = snapshot.getValue(MessageEntity::class.java)
if (message != null)
trySend(Response.Success(mapOf(ADDED to message )))
else
trySend(Response.Success(mapOf(RESPONSE_NULL to MessageEntity())))
}
override fun onChildChanged(
snapshot: DataSnapshot,
previousChildName: String?
) {
val message = snapshot.getValue(MessageEntity::class.java)
if (message != null)
trySend(Response.Success(mapOf(CHANGED to message)))
else
trySend(Response.Success(mapOf(RESPONSE_NULL to MessageEntity())))
}
override fun onChildRemoved(snapshot: DataSnapshot) {
}
override fun onChildMoved(
snapshot: DataSnapshot,
previousChildName: String?
) {
}
override fun onCancelled(error: DatabaseError) {
}
} )
awaitClose {
reference.removeEventListener(subscription)
channel.close()
}
why this error occurred? is it due to callbackFlow??
Issue (as you can see, the window does not show the end of messages. even usage of setStackFromEnd)
What I expected and wanted
Try this binding.rvChatMessage.layoutManager = LinearLayoutManager(this ,RecyclerView.VERTICAL,true) and remove stackfromend
My problem is that I want to select an item in a RecyclerView and it should change the color and if I click on another the first selected item should change to the default color (and the last clicked should have the selected color).
I have already a color change of the selected one and if I click on the selected one again it changes to default color. Now I am only missing that if I click on an unselected item and if I have already a selected item they "switch" the color
This is my SubItem class:
class SubItem(val channel: Channel) : Item<GroupieViewHolder>() {
#SuppressLint("ResourceAsColor")
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
val profileImageUrl = channel.channel_logo
viewHolder.itemView.sub_item_name.text = channel.channel_name
viewHolder.itemView.sub_item_layout.setBackgroundResource(R.color.white)
viewHolder.itemView.sub_item_name.setTextColor(R.color.colorSecondaryText)
val targetImageView = viewHolder.itemView.sub_item_profile
try {
Picasso.get().load(profileImageUrl)
.placeholder(R.drawable.ic_baseline_account_circle_24)
.into(targetImageView)
}catch (e:Exception){
Log.d("SubItem","${e.message}")
}
viewHolder.itemView.sub_item_layout.setOnClickListener {
if (selected_position == position){
selected_position = null
viewHolder.itemView.sub_item_layout.setBackgroundResource(R.color.white)
viewHolder.itemView.sub_item_name.setTextColor(R.color.colorSecondaryText)
}
else{
selected_position = position
viewHolder.itemView.sub_item_layout.setBackgroundResource(R.color.colorSecondaryText)
viewHolder.itemView.sub_item_name.setTextColor(R.color.black)
}
}
}
override fun getLayout(): Int {
return R.layout.subscription_item
}
}
If it is helping here is my function where I add the items to the RecyclerView
private fun fetchSubs() {
val uid = auth.uid
val user = database.getReference("/users/$uid/subscriptions")
val adapter = GroupAdapter<GroupieViewHolder>()
user.addListenerForSingleValueEvent(object : ValueEventListener{
#SuppressLint("NotifyDataSetChanged")
override fun onDataChange(p0: DataSnapshot) {
p0.children.forEach{
val sub = it.getValue(Subscription::class.java) ?: return
if (sub.subscribed == true) {
val ref = database.getReference("/channels/${sub.channel_uid}")
ref.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onDataChange(p0: DataSnapshot) {
val channel = p0.getValue(Channel::class.java) ?: return
adapter.add(SubItem(channel))
}
override fun onCancelled(error: DatabaseError) {
}
})
}
}
adapter.setOnItemClickListener{ item, view ->
val subItem = item as SubItem
val channelName = subItem.channel.channel_name
val channelUid = subItem.channel.uid
Toast.makeText(requireContext(),"$channelName : $channelUid", Toast.LENGTH_SHORT).show()
fetchSubs()
}
sub_recyclerview.adapter = adapter
}
override fun onCancelled(error: DatabaseError) {
}
})
}
sorry that I am not using Models and Adapter
For everyone who uses groupie like me this could help you in future.
This is my solution for my Problem.
SubItem class
class SubItem(val channel: Channel) : Item<GroupieViewHolder>() {
#SuppressLint("ResourceAsColor")
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
val profileImageUrl = channel.channel_logo
viewHolder.itemView.sub_item_name.text = channel.channel_name
val targetImageView = viewHolder.itemView.sub_item_profile
try {
Picasso.get().load(profileImageUrl)
.placeholder(R.drawable.ic_baseline_account_circle_24)
.into(targetImageView)
}catch (e:Exception){
Log.d("SubItem","${e.message}")
// Toast.makeText(,e.message,Toast.LENGTH_SHORT).show()
}
checkFilter(viewHolder,position)
}
#SuppressLint("ResourceAsColor")
private fun checkFilter(v: GroupieViewHolder, p: Int) {
when (SubscriptionsFragment.list[p]) {
true -> {
v.itemView.sub_item_layout.setBackgroundResource(R.color.colorDivider)
v.itemView.sub_item_name.setTextColor(R.color.black)
}
false -> {
v.itemView.sub_item_layout.setBackgroundResource(R.color.white)
v.itemView.sub_item_name.setTextColor(R.color.colorSecondaryText)
}
}
}
override fun getLayout(): Int {
return R.layout.subscription_item
}
}
My function with setOnItemClickListener
private fun fetchSubs() {
val uid = auth.uid
val user = database.getReference("/users/$uid/subscriptions")
val adapter = GroupAdapter<GroupieViewHolder>()
user.addListenerForSingleValueEvent(object : ValueEventListener{
#SuppressLint("NotifyDataSetChanged")
override fun onDataChange(p0: DataSnapshot) {
list = mutableListOf()
p0.children.forEach{
val sub = it.getValue(Subscription::class.java) ?: return
if (sub.subscribed == true) {
val ref = database.getReference("/channels/${sub.channel_uid}")
ref.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onDataChange(p0: DataSnapshot) {
val channel = p0.getValue(Channel::class.java) ?: return
list.add(false) // created in companion object: var list = mutableListOf<Boolean>()
oldList.add(false) // created in companion object: var oldlist = mutableListOf<Boolean>()
adapter.add(SubItem(channel))
}
override fun onCancelled(error: DatabaseError) {
}
})
}
}
adapter.setOnItemClickListener{ item, view ->
val subItem = item as SubItem
val pos = adapter.getAdapterPosition(subItem)
// Here happens the magic
list[pos] = !list[pos] // change selected item from false to true or from true to false
val l = list[pos] // saving Boolean
list = mutableListOf()
oldList.forEach{ // using oldList to loop so many times I need
list.add(false) // setting all to false
}
if (l){ // if Boolean is true
list[pos] = !list[pos] // change selected item from false to true
}
val channelUid = subItem.channel.uid
fetchVideos(channelUid)
adapter.notifyDataSetChanged() // refresh all items in SubItem
}
try {
sub_recyclerview.adapter = adapter
} catch(e:Exception){
Log.d("fetchSubs","${e.message}")
}
}
override fun onCancelled(error: DatabaseError) {
}
})
}
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 load just one element with one scroll gesture. Now it is like one scroll gesture loads 1 or few new elements (depends on time of scroll gesture). As a solution I could do this gesture in shorter time than 500ms or make this postDelayed's delay longer but I guess there are better solutions for that. Do you have any ideas how to do that?
This app is written in MVP pattern. Here is my code:
CurrencyFragmentList.kt
private fun addScrollerListener() {
rvItem.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(_rvItem: RecyclerView, newState: Int) {
super.onScrollStateChanged(_rvItem, newState)
Log.e("scroll", isLoading.toString())
if (!isLoading) {
if (!_rvItem.canScrollVertically(1)) {
loadMore()
isLoading = true
}
}
}
})
}
private fun loadMore() {
showProgressDialog()
var numberOfDays = mainPresenter.getNumberOfMinusDays()
numberOfDays++
mainPresenter.saveNumberOfMinusDaysIntoSp(numberOfDays)
var dateMinusXDays = mainPresenter.currentDateMinusXDaysToStr(numberOfDays)
val nextLimit = listSize + 1
for (i in listSize until nextLimit) {
if (mainPresenter.checkIfSuchDateExistsinSp(dateMinusXDays)) {
Log.d("such date already exists in shared prefs", dateMinusXDays)
handler.postDelayed({
mainPresenter.processDateWithoutMakingACall(dateMinusXDays)
}, 500)
} else {
mainPresenter.makeACall(dateMinusXDays)
Log.d("retrofit call made", dateMinusXDays)
}
}
itemAdapter.notifyDataSetChanged()
}
override fun hideProgressDialog() {
if (apiResponseList.size > 1) {
apiResponseList.removeAt(apiResponseList.size - 1)
listSize = apiResponseList.size
itemAdapter.notifyItemRemoved(listSize)
} else progress_bar.visibility = View.GONE
isLoading = false
}
override fun assignResponseToRecyclerview(apiResponse: ApiResponse?) {
rvItem.apply {
layoutManager = _layoutManager
apiResponseList.add(apiResponse!!)
itemAdapter = activity?.let { ItemAdapter(apiResponseList, it) }!!
adapter = itemAdapter
}
Log.e("assign", isLoading.toString())
}
MainPresenter.kt
override fun makeACall(date: String?) {
//view.showProgressDialog()
date?.let { restModel.fetchApiResponse(this, it) }
}
fun processDateWithoutMakingACall(date: String) {
val apiResponse = processRawJson(sp.getString(date, "").toString())
passResponseToView(apiResponse)
}
override fun processRawJson(rawJson: String): ApiResponse {
val parser = JsonParser()
val rootObj = parser.parse(rawJson).asJsonObject
var ratesObj = JsonObject()
var ratesKeys: Set<String> = HashSet()
val ratesArrayList: ArrayList<Currency> = ArrayList()
val rootKeys = rootObj.keySet();
var baseValue = ""
var dateValue = ""
for (key in rootKeys) {
if (key == "base")
baseValue = rootObj.get(key).asString
if (key == "date")
dateValue = rootObj.get(key).asString
if (key == "rates") {
ratesObj = rootObj.get(key).asJsonObject
ratesKeys = ratesObj.keySet()
}
}
for (key2 in ratesKeys) {
Log.e("ratesKey", key2)
Log.e("ratesValue", ratesObj.get(key2).asFloat.toString())
ratesArrayList.add(Currency(key2, ratesObj.get(key2).asFloat))
}
saveRawJsonIntoSp(rawJson, dateValue)
return ApiResponse(baseValue, dateValue, ratesArrayList, false)
}
override fun passResponseToView(apiResponse: ApiResponse?) {
view.hideProgressDialog()
view.assignResponseToRecyclerview(apiResponse)
}
RestModel.kt
override fun fetchApiResponse(presenter: MainPresenter, date: String) {
job = CoroutineScope(Dispatchers.IO).launch {
val response = userService.getCurrenciesForDate(date)
withContext(Dispatchers.Main) {
if (response.isSuccessful) {
val rawJson = response.body()
val apiResponse = presenter.processRawJson(rawJson)
presenter.passResponseToView(apiResponse)
}
}
}
}
Any help will be really appreciated. Thank you in advance!
Try out the SnapHelper, it might slow layout manager to make more callbacks and stop overloading
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)