I am using a room database with live data
Retrieving information from Database
private fun getData(query: String) {
var dataIssueJson: MutableList<DataIssue>
dataViewModel.searchDatabase(query).observe(this, {
dataList = it as MutableList<Data>
data = if (dataList.isNotEmpty())
dataList[0] else
Data()
val gson = Gson()
val dataIssueString =
if (dataList.isNotEmpty()) {
dataList[0].dataIssue
} else ""
val type = object : TypeToken<MutableList<DataIssue>>() {}.type
dataIssueJson = if (dataIssueString.isNotEmpty())
gson.fromJson(dataIssueString, type)
else
mutableListOf()
viewAdapter.clear()
initRecyclerView(dataIssueJson.toDataIssueItem())
val dataStatus = if (dataList.isNotEmpty())
dataList[0].dataStatus
else "Status"
dataViewModel.dataStatus.value = dataStatus
colorStatus(dataStatus)
})
}
Recyclerview
private fun initRecyclerView(dataItem: List<dataIssueItem>) {
binding.recyclerViewDataIssues.apply {
layoutManager = LinearLayoutManager(this#MainActivity)
adapter = viewAdapter.apply {
addAll(botItem)
setOnItemClickListener(onClickItem)
notifyDataSetChanged()
}
scrollToPosition(binding.recyclerViewDataIssues.adapter!!.itemCount - 1)
}
}
private fun List<DataIssue>.toDataIssueItem(): List<DataIssueItem> {
return this.map { DataIssueItem(it) }
}
OnClick
private val onClickItem = OnItemClickListener { item, view ->
if (item is DatatIssueItem) {
Log.d(AppConstants.MAIN_ACTIVITY_TAG, "DataIssue:${item.dataIssue.dataIssue}, date&time: ${item.dataIssue.dateAndTimeOfIssue}")
}
}
The Recyclerview works fine, but when I click on the Recyclerview Item it returns an empty Item and I'm just not sure why
Yeah, I figured it out. The data comes from the Item itself, so make sure to let the data be accessible from the Item.
Related
inside MainActivity I have snapShot listener to document added that calls a function inside a fragment that supose to set and update the adapter of item that stored in fire store
mFireStore.collection(Constans.BOARDS)
.whereArrayContains(Constans.ASSIGNED_TO,FireStore().getCurrentUid())
.orderBy("timeStamp", Query.Direction.DESCENDING)
.addSnapshotListener { value, e ->
Log.d("MainActivity","board listener")
if (e != null) {
Log.w(ContentValues.TAG, "Listen failed.", e)
return#addSnapshotListener
}
val boards = ArrayList<Board>()
Constans.BOARDS_CHATS_LIST = ArrayList()
for (doc in value!!) {
val board = doc.toObject(Board()::class.java)
Constans.BOARDS_CHATS_LIST.add(board)
}
fragment_chat().updateBoardToUi(Constans.BOARDS_CHATS_LIST)
}
and here is the function
fun updateBoardToUi(boardsChatsList: ArrayList<Board>) {
if(boardsChatsList.size > 0){
val context = getContext() ?: return
Log.e("${Constans.BOARDS_CHATS_LIST.size.toString()}","updateBoardToUi")
view?.rv_chats_list?.visibility = View.VISIBLE
view?.no_chats_avlible?.visibility = View.GONE
view?.rv_chats_list?.layoutManager = LinearLayoutManager(context)
view?.rv_chats_list?.setHasFixedSize(true)
//might be an error
adapter = BoardItemsAdapter(context,Constans.BOARDS_CHATS_LIST)
view?.rv_chats_list?.adapter = adapter
adapter.notifyItemInserted(0)
adapter.setOnClickListener(
object :BoardItemsAdapter.OnClickListener{
override fun onClick(position: Int, model: Board) {
Log.i("fragment chat", "on click")
val intent = Intent(context, ChatActivity::class.java)
intent.putExtra(Constans.BOARD_CHAT_DETAILS, model)
intent.putExtra("uid", FirebaseAuth.getInstance().currentUser?.uid )
intent.putExtra(Constans.DOCUMENT_ID, model.documentId)
intent.putExtra("position", position)
startActivity(intent)
}
}
)
}else{
Log.e("inside","updateBoardToUi2")
view?.no_chats_avlible?.visibility = View.VISIBLE
}
}
but the adapter deas not show the new item added even thogh I use adapter.notifyItemInserted(0)
It is because you can not hold and send data with "Constants.BOARDS_CHATS_LIST". Because every time you want to call it, it will return the default value it has. You can do 4 things that come into my mind:
1- Send the data from activity to fragment via Shared Preferences. I do not recommend this method.
2 - Send data from activity to fragment via bundle. This is doable but i do not prefer it.
3 - Move your firestore function to the fragment and declare a global list and put the records there, then use it in updateBoardToUi function. You can do this but if you need this function in other fragment, you need to copy and paste it there too.
4- You can create a new class for firestore functions, and whenever you need it, call it from there. This is the best way and i will try to help you with it.
Create new kotlin class and paste this inside it. You will later call this inside onViewCreated of your fragment, and it will send the array to the updateBoardToUi method.
class FirestoreClass {
private val mFireStore = FirebaseFirestore.getInstance()
private val mFirebaseAuth = FirebaseAuth.getInstance()
fun getBoards(fragment: YourFragmentName) {
mFireStore.collection(Constans.BOARDS)
.whereArrayContains(Constans.ASSIGNED_TO,getCurrentUserID())
.orderBy("timeStamp", Query.Direction.DESCENDING)
.addSnapshotListener { value, e ->
if (e != null) {
Log.w(ContentValues.TAG, "Listen failed.", e)
return#addSnapshotListener
}
val boards = ArrayList<Board>()
for (doc in value!!) {
val board = doc.toObject(Board()::class.java)
boards.add(board)
}
fragment.updateBoardToUi(boards)
}
}
fun getCurrentUserID(): String {
val currentUser = mFirebaseAuth.currentUser
var currentUserID = ""
if (currentUser != null) {
currentUserID = currentUser.uid
}
return currentUserID
}
}
Now we will use the list from your db.
fun updateBoardToUi(boardsChatsList: ArrayList<Board>) {
// fragment.updateBoardToUi(boards) that sent the data and now
// it is in boardsChatsList, you will use this.
if(boardsChatsList.size > 0){
val context = getContext() ?: return
Log.e("${boardsChatsList.size.toString()}","updateBoardToUi")
view?.rv_chats_list?.visibility = View.VISIBLE
view?.no_chats_avlible?.visibility = View.GONE
adapter = BoardItemsAdapter(context,boardsChatsList)
view?.rv_chats_list?.adapter = adapter
view?.rv_chats_list?.layoutManager = LinearLayoutManager(context)
view?.rv_chats_list?.setHasFixedSize(true)
adapter.setOnClickListener(
object :BoardItemsAdapter.OnClickListener{
override fun onClick(position: Int, model: Board) {
Log.i("fragment chat", "on click")
val intent = Intent(context,ChatActivity::class.java)
intent.putExtra(Constans.BOARD_CHAT_DETAILS, model)
intent.putExtra("uid", FirestoreClass().getCurrentUserID())
intent.putExtra(Constans.DOCUMENT_ID, model.documentId)
intent.putExtra("position", position)
startActivity(intent)
}
}
)
}else{
Log.e("inside","updateBoardToUi2")
view?.no_chats_avlible?.visibility = View.VISIBLE
}
}
And finally call that db function in your fragment's onViewCreated to activate all of this. If you do not have onViewCreated just paste this code:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
FirestoreClass().getUsersList(this)
}
All of this can be too much, but this is the best practice. If you learn this convention, you will easily adapt working anywhere.
I am pulling some data from the firestore and after that, I want to filter it before adding it to the recyclerView. Is that possible?
The data that I have pulled is an ArrayList which has a field called 'order_status'. It may contain many different statuses, but I want to filter it out so that I will be left with only "Pending", "Order Received", "In Process", "Packed".
The following code is used to pull the data from the firestore
fun getOrderStatusList(fragment: OrdersByStatusFragment) {
mFireStore.collection("orders")
.whereIn(
"address.pinCode",
listOf("676767", "652365","679577")
)
.get()
.addOnSuccessListener { document ->
val list: ArrayList<OrderStatus> = ArrayList()
for (i in document.documents) {
val orderStatus = i.toObject(OrderStatus::class.java)!!
orderStatus.id = i.id
list.add(orderStatus)
}
fragment.successOrderStatusList(list)
}
.addOnFailureListener {
fragment.hideProgressDialog()
}
}
The following code is part of the fragment.
fun successOrderStatusList(orderStatusList: ArrayList<OrderStatus>) {
hideProgressDialog()
if (orderStatusList.size > 0) {
rv_order_by_status.visibility = View.VISIBLE
tv_no_orders_by_status_found.visibility = View.GONE
rv_order_by_status.layoutManager = LinearLayoutManager(activity)
rv_order_by_status.setHasFixedSize(true)
val orderStatusListAdapter =
OrderStatusListAdapter(requireActivity(), orderStatusList,this#OrdersByStatusFragment)
rv_order_by_status.adapter = orderStatusListAdapter
} else {
rv_order_by_status.visibility = View.GONE
tv_no_orders_by_status_found.visibility = View.VISIBLE
}
}
In your case, you should just check for status inside the loop.
F.e:
if(document.orderStatus == "Pending")
//addTolist
I'm using the android paging library to show search result items, is there any way I can clear/remove all the loaded result items, Calling Invalidate on live Paged List refreshing the list not clear/remove items
In Activity:
fun clearSearchResult() {
if (productSearchResultItemAdapter.itemCount > 0) {
viewModel.invalidateResultList()
}
}
In ViewModel
private fun searchProductByTerm(searchTerm: String): Listing<Item> {
sourceFactory = ProductSearchItemDataSourceFactory(productSearchUC, compositeDisposable, searchTerm, resourceResolver)
val livePagedList = LivePagedListBuilder(sourceFactory, pagedListConfig)
//The executor used to fetch additional pages from the DataSource
.setFetchExecutor(getNetworkExecutor())
.build()
return Listing(
pagedList = livePagedList,
networkState = switchMap(sourceFactory.sourceLiveData) {
it.networkState
},
retry = {
sourceFactory.sourceLiveData.value?.retryAllFailed()
}
)
}
fun invalidateResultList() {
sourceFactory?.sourceLiveData?.value?.invalidate()
}
private val productSearchName = MutableLiveData<String>()
private val repoResult = map(productSearchName) {
searchProductByTerm(it)
}
If you're working with PagingDataAdapter, searchAdapter.submitData(lifecycle, PagingData.empty()) works
submitting null clear the currently loaded page list
productSearchResultItemAdapter.submitList(null)
In Java:
I cleared all items on in PagedListAdapter by calling invalidate() on DataSource instance like that
public void clear(){
movieDataSource.invalidate();
}
Add this method in your ViewModel then call it in your activity
movieViewModel.clear();
movieAdapter.notifyDataSetChanged();
Then Load any data you want
You can see how I made it in my project.
Here is the Link: https://github.com/Marwa-Eltayeb/MovieTrailer
In Fragment
lifecycleScope.launch {
viewModel.currentResult = null
viewModel.getSearchAudio(binding.etxtSearch.text.toString().trim(), 0).collectLatest { it ->
Log.v(mTAG, "Status: New record")
adapterAudioList.submitData(it)
}
}
In ViewModel
var currentResult: Flow<PagingData<AudioModel>>? = null
fun getSearchAudio(trackName: String, lastPageCount: Int): Flow<PagingData<AudioModel>> {
val lastResult = currentResult
if (lastResult != null) {
return lastResult
}
val newResult: Flow<PagingData<AudioModel>> = videoRepository.getAudioSearchPaging(trackName, lastPageCount).cachedIn(viewModelScope)
currentResult = newResult
return newResult
}
In videoRepository
fun getAudioSearchPaging(trackName: String, lastPageCount: Int): Flow<PagingData<AudioModel>> {
return Pager(
config = PagingConfig(pageSize = KeyConstants.AUDIO_PAGE_SIZE, enablePlaceholders = false),
pagingSourceFactory = { AudioSearchPagingSource(context, trackName, lastPageCount) },
).flow
}
Before invalidate, clear your list data item.
Like we did in simple way:
list.clear();
adapter.notifyDataSetChanged();
I have List = {1,2}, I want to retrieve data of child ("deals/{1,2})
fun deal_detail(deal: List<Deals>): Flowable<List<Deal_detail>> =
observeValueEvent(ref1.child("deals")).map { snapshot ->
snapshot.children.mapNotNull { child ->
var name = child.child("$deal/dealable/keyword").getValue().toString()
Deal_detail(name)
}
}
i solve the problem use map for list data.....
ref: https://medium.com/#justintulk/how-to-query-arrays-of-data-in-firebase-aa28a90181ba
fun deal_detail(deal: List<Deals>): Flowable<Deal_detail> =
observeValueEvent(ref1.child("deals")).map {
snapshot->
var deal1 = deal.map { id ->
snapshot.child(id.name.toString()).child("/dealable/keyword").getValue().toString()
}
var image=deal.map { id->
snapshot.child(id.name.toString()).child("dealable/image").getValue().toString()
}
Deal_detail(deal1,image)
}
I'm using Google Books API for searching books. But problem is that when I want my ListView to be populated with books, I get an error. This error is pointed onPostExecute, but I can't figured out what's the problem.
kotlin.KotlinNullPointerException
at com.example.claudiu.reader.Fragments.ISBNFragment$FetchBookTask.onPostExecute(ISBNFragment.kt:137)
at com.example.claudiu.reader.Fragments.ISBNFragment$FetchBookTask.onPostExecute(ISBNFragment.kt:56)
Here is where I set my adapter :
override fun onPostExecute(books: List<Book>?) {
if (books != null) {
adapter!!.clear()
for (book in books) {
adapter!!.add(book)
}
}
}
And here is all code where I'm parsing the JSON:
#Throws(JSONException::class)
private fun getBookDataFromJson(booksJsonStr: String?): List<Book> {
val books = ArrayList<Book>()
val API_RESULT_ITEMS_ARRAY = "items"
val API_VOLUME_INFO = "volumeInfo"
val API_BOOK_TITLE = "title"
val API_BOOK_IMAGE_LINKS = "imageLinks"
val API_BOOK_SMALL_THUMBNAIL = "smallThumbnail"
val API_BOOK_AUTHORS_ARRAY = "authors"
val booksJson = JSONObject(booksJsonStr)
val itemsArray = booksJson.getJSONArray(API_RESULT_ITEMS_ARRAY)
for (i in 0 until itemsArray.length()) {
val item = itemsArray.getJSONObject(i)
val volumeInfo = item.getJSONObject(API_VOLUME_INFO)
val bookTitle = volumeInfo.getString(API_BOOK_TITLE)
val imageLinksSB = StringBuilder()
if (!volumeInfo.isNull(API_BOOK_IMAGE_LINKS)) {
val imageLinks = volumeInfo.getJSONObject(API_BOOK_IMAGE_LINKS)
imageLinksSB.append(imageLinks.getString(API_BOOK_SMALL_THUMBNAIL))
} else {
imageLinksSB.append("-1")
}
val bookImageLink = imageLinksSB.toString()
val authorsSB = StringBuilder()
if (!volumeInfo.isNull(API_BOOK_AUTHORS_ARRAY)) {
val authorsArray = volumeInfo.getJSONArray(API_BOOK_AUTHORS_ARRAY)
for (k in 0 until authorsArray.length()) {
authorsSB.append(authorsArray.getString(k))
authorsSB.append(getString(R.string.comma))
}
} else {
authorsSB.append(getString(R.string.unknown_error))
}
val bookAuthors = authorsSB.toString()
books.add(Book(bookTitle, bookAuthors, bookImageLink))
}
Log.d(LOG_TAG, "BOOKS : $books")
return books
}
I couldn't find any thing to help me and I have no idea what should I do.
Your problem is the place where you declare your adapter, I had this problem because declaration of my adapter was in the wrong place.
You should move adapter declaration from onCreatView into onViewCreated and everything will work fine.
Hope it helps !