Let's define one variable in the begin, it will be more easier to read:
sqlite3 column KEY_TITLE = A
I have activity in conduction with cursor loader. When there are no rows in column A I want activity to display an emptyView. To do that, I created a method which checks all rows in column A if there is any data in it.
fun hideLayout(){
val isEmpty = emptyViewObserver(applicationContext).isEmpty()
if (isEmpty) {
recyclerView.visibility = View.GONE
emptyView.visibility = View.VISIBLE
} else {
recyclerView.visibility = View.VISIBLE
emptyView.visibility = View.GONE
}
}
class emptyViewObserver with isEmpty method:
class emptyViewObserver(val context: Context){
fun isEmpty():Boolean{
val db = WalletDbHelper(context).readableDatabase
val projection = arrayOf(KEY_TITLE)
val cursor = db.query(
WalletEntry.TABLE_NAME,
projection,
null,
null,
null,
null,
null
)
val names = mutableListOf<String>()
with(cursor){
while (moveToNext()){
val name = getString(cursor.getColumnIndexOrThrow(WalletEntry.KEY_TITLE))
names.add(name)
}
}
db.close()
return names.isEmpty()
}
}
Whenever I perform any CRUD operation in MainActivity class I call hideLayout method to check out if emptyView should be shown or not. It works fine. But I'm facing problem after I perform delete operation from my adapter to call hideLayout() method. For obvious reasons it throws me error when calling it directly from adapter so I though about implementing interface and overriding this in MainActivity. I read some tutorials but I still didn't figure out how to implement this properly.
This is my delete method from adapter:
holder.delete.setOnClickListener { v ->
val popup = PopupMenu(context, v)
val inflater = popup.menuInflater
inflater.inflate(R.menu.delete, popup.menu)
popup.show()
popup.setOnMenuItemClickListener { v ->
when(v.itemId) {
R.id.delete -> {
delete(uri, walletName)
true
}
else -> {
false
}
}
}
}
and delete():
fun delete(uri: String, walletName: String){
val newUri: Uri = Uri.parse(uri)
context.contentResolver.delete(newUri, WalletEntry.KEY_TITLE, arrayOf(walletName))
}
Do you have any ideas how can I write this code?
You need a callback, When you delete some item, check if is the last one and if it is true call that.
Add this interface on your adapter:
interface OnListIsEmpty{
fun onListIsEmpty()
}
And create a object of this like global variable in your adapter too:
private var mOnListIsEmpty: OnListIsEmpty? = null
Add a setter method:
fun setOnListIsEmpty(onListIsEmpty: OnListIsEmpty){
this.mOnListIsEmpty = onListIsEmpty
}
And in your delete method do the validation if is empty if it is true then:
mOnListIsEmpty?.onListIsEmpty()
And not forget set the callback when you initilize the adapter with:
adapter.setOnListIsEmpty(object : OnListIsEmpty{
override fun onListIsEmpty() {
//your action
}
})
Related
I'm newbie use Kotlin on my dev apps android,
and now, I on step learn to implement Update UI with LiveData, Retrofit, Coroutine on Recyclerview. The My Apps:
MainActivity > MainFragment with 3 Tab fragment > HomeFragment, DashboardFragment, and SettingsFragment
I call function to get data from server on onCreateView HomeFragment, and observe this with show shimmer data on my Recylerview when is loading, try update RecyclerView when success, and show view Failed Load -button refresh when error.
The problem is:
Adapter Recyclerview not Update when success get Data from Server. Adapter still show shimmer data
With case error (no Internet), i show view Failed Load, with button refresh. Tap to refresh, i re-call function to get data server, but fuction not work correct. Recyclerview show last data, not show Failed Load again.
Bellow my code
HomeFragment
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!
private lateinit var adapterNews: NewsAdapter
private var shimmerNews: Boolean = false
private var itemsDataNews = ArrayList<NewsModel>()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout for this fragment
_binding = FragmentHomeBinding.inflate(inflater, container, false)
......
newsViewModel = ViewModelProvider(requireActivity()).get(NewsViewModel::class.java)
//news
binding.frameNews.rvNews.setHasFixedSize(true)
binding.frameNews.rvNews.layoutManager = llmh
adapterNews = NewsAdapter(itemsDataNews, shimmerNews)
binding.frameNews.rvNews.adapter = adapterNews
// Observe
//get News
newsViewModel.refresh()
newsViewModel.newsList.observe(
viewLifecycleOwner,
androidx.lifecycle.Observer { newsList ->
newsList?.let {
binding.frameNews.rvNews.visibility = View.VISIBLE
binding.frameNews.rvNews.isNestedScrollingEnabled = true
binding.frameNews.itemNewsLayoutFailed.visibility = View.GONE
if (it.size == 0)
binding.frameNews.root.visibility = View.GONE
else
getDataNews(it)
}
})
newsViewModel.loading.observe(viewLifecycleOwner) { isLoading ->
isLoading?.let {
binding.frameNews.rvNews.visibility = View.VISIBLE
binding.frameNews.rvNews.isNestedScrollingEnabled = false
binding.frameNews.itemNewsLayoutFailed.visibility = View.GONE
getDataNewsShimmer()
}
}
newsViewModel.loadError.observe(viewLifecycleOwner) { isError ->
isError?.let {
binding.frameNews.rvNews.visibility = View.INVISIBLE
binding.frameNews.itemNewsLayoutFailed.visibility = View.VISIBLE
binding.frameNews.btnNewsFailed.setOnClickListener {
newsViewModel.refresh()
}
}
}
....
return binding.root
}
#SuppressLint("NotifyDataSetChanged")
private fun getDataNewsShimmer() {
shimmerNews = true
itemsDataNews.clear()
itemsDataNews.addAll(NewsData.itemsShimmer)
adapterNews.notifyDataSetChanged()
}
#SuppressLint("NotifyDataSetChanged")
private fun getDataNews(list: List<NewsModel>) {
Toast.makeText(requireContext(), list.size.toString(), Toast.LENGTH_SHORT).show()
shimmerNews = false
itemsDataNews.clear()
itemsDataNews.addAll(list)
adapterNews.notifyDataSetChanged()
}
override fun onDestroyView() {
super.onDestroyView()
_binding=null
}
NewsViewModel
class NewsViewModel: ViewModel() {
val newsService = KopraMobileService().getNewsApi()
var job: Job? = null
val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
onError("Exception handled: ${throwable.localizedMessage}")
}
val newsList = MutableLiveData<List<NewsModel>>()
val loadError = MutableLiveData<String?>()
val loading = MutableLiveData<Boolean>()
fun refresh() {
fetchNews()
}
private fun fetchNews() {
loading.postValue(true)
job = CoroutineScope(Dispatchers.IO + exceptionHandler).launch {
val response = newsService.getNewsList()
withContext(Dispatchers.Main) {
if (response.isSuccessful) {
newsList.postValue(response.body()?.data)
loadError.postValue(null)
loading.postValue(false)
} else {
onError("Error : ${response.message()} ")
}
}
}
loadError.postValue("")
loading.postValue( false)
}
private fun onError(message: String) {
loadError.postValue(message)
loading.postValue( false)
}
override fun onCleared() {
super.onCleared()
job?.cancel()
}
}
NewsAdapter
NewsAdapter(
var itemsCells: List<NewsModel?> = emptyList(),
var shimmer: Boolean ,
) :
RecyclerView.Adapter<ViewHolder>() {
private lateinit var context: Context
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NewsHomeViewHolder {
context = parent.context
return NewsHomeViewHolder(
NewsItemHomeBinding.inflate(LayoutInflater.from(parent.context), parent, false)
)
}
override fun onBindViewHolder(holder: NewsHomeViewHolder, position: Int) {
holder.bind(itemsCells[position]!!)
}
inner class NewsHomeViewHolder(private val binding: NewsItemHomeBinding) :
ViewHolder(binding.root) {
fun bind(newsItem: NewsModel) {
binding.newsItemFlat.newsTitle.text = newsItem.name
binding.newsItemFlatShimmer.newsTitle.text = newsItem.name
binding.newsItemFlat.newsSummary.text = newsItem.name
binding.newsItemFlatShimmer.newsSummary.text = newsItem.name
if (shimmer) {
binding.layoutNewsItemFlat.visibility = View.INVISIBLE
binding.layoutNewsItemFlatShimmer.visibility = View.VISIBLE
binding.layoutNewsItemFlatShimmer.startShimmer()
} else {
binding.layoutNewsItemFlat.visibility = View.VISIBLE
binding.layoutNewsItemFlatShimmer.visibility = View.INVISIBLE
}
}
}
override fun getItemCount(): Int {
return itemsCells.size
}
I hope someone can help me to solve the problem. thanks, sorry for my English.
You have 3 different LiveDatas, right? One with a list of news data, one with a loading error message, and one with a loading state.
You set up an Observer for each of these, and that observer function gets called whenever the LiveData's value updates. That's important, because here's what happens when your request succeeds, and you get some new data:
if (response.isSuccessful) {
newsList.postValue(response.body()?.data)
loadError.postValue(null)
loading.postValue(false)
}
which means newsList updates, then loadError updates, then loading updates.
So your observer functions for each of those LiveDatas run, in that order. But you're not testing the new values (except for null checks), so the code in each one always runs when the value updates. So when your response is successful, this happens:
newsList observer runs, displays as successful, calls getDataNews
loadError observer runs, value is null so nothing happens
loading observer runs, value is false but isn't checked, displays as loading, calls getDataNewsShimmer
So even when the response is successful, the last thing you do is display the loading state
You need to check the state (like loading) before you try to display it. But if you check that loading is true, you'll have a bug in fetchNews - that sets loading = true, starts a coroutine that finishes later, and then immediately sets loading = false.
I'd recommend trying to create a class that represents different states, with a single LiveData that represents the current state. Something like this:
// a class representing the different states, and any data they need
sealed class State {
object Loading : State()
data class Success(val newsList: List<NewsModel>?) : State()
data class Error(val message: String) : State()
}
// this is just a way to keep the mutable LiveData private, so it can't be updated
private val _state = MutableLiveData<State>()
val state: LiveData<State> get() = _state
private fun fetchNews() {
// initial state is Loading, until we get a response
_state.value = State.Loading
job = CoroutineScope(Dispatchers.IO + exceptionHandler).launch {
val response = newsService.getNewsList()
// if you're using postValue I don't think you need to switch to Dispatchers.Main?
_state.postValue(
// when you get a response, the state is now either Success or Error
if (response.isSuccessful) State.Success(response.body()?.data)
else State.Error("Error : ${response.message()} ")
)
}
}
And then you just need to observe that state:
// you don't need to create an Observer object, you can use a lambda!
newsViewModel.state.observe(viewLifecycleOwner) { state ->
// Handle the different possible states, and display the current one
// this lets us avoid repeating 'binding.frameNews' before everything
with(binding.frameNews) {
// You could use a when block, and describe each state explicitly,
// like your current setup:
when(state) {
// just checking equality because Loading is a -singleton object instance-
State.Loading -> {
rvNews.visibility = View.VISIBLE
rvNews.isNestedScrollingEnabled = false
itemNewsLayoutFailed.visibility = View.GONE
getDataNewsShimmer()
}
// Error and Success are both -classes- so we need to check their type with 'is'
is State.Error -> {
rvNews.visibility = View.INVISIBLE
itemNewsLayoutFailed.visibility = View.VISIBLE
btnNewsFailed.setOnClickListener {
newsViewModel.refresh()
}
}
is State.Success -> {
rvNews.visibility = View.VISIBLE
rvNews.isNestedScrollingEnabled = true
itemNewsLayoutFailed.visibility = View.GONE
// Because we know state is a Success, we can access newsList on it
// newsList can be null - I don't know how you want to handle that,
// I'm just treating it as defaulting to size == 0
// (make sure you make this visible when necessary too)
if (state.newsList?.size ?: 0 == 0) root.visibility = View.GONE
else getDataNews(state.newsList)
}
}
// or, if you like, you could do this kind of thing instead:
itemNewsLayoutFailed.visibility = if (state is Error) VISIBLE else GONE
}
}
You also might want to break that display code out into separate functions (like showError(), showList(state.newsList) etc) and call those from the branches of the when, if that makes it more readable
I hope that makes sense! When you have a single value representing a state, it's a lot easier to work with - set the current state as things change, and make your observer handle each possible UI state by updating the display. When it's Loading, make it look like this. When there's an Error, make it look like this
That should help avoid bugs where you update multiple times for multiple things, trying to coordinate everything. I'm not sure why you're seeing that problem when you reload after an error, but doing this might help fix it (or make it easier to see what's causing it)
I have created a variable private var deals=ArrayList<Deals>() in a fragment and I have set a click listener in the onCerateView() like the following.
binding.tvAllDeals.setOnClickListener {
viewAllDeals()
}
So that it will trigger the following method
private fun viewAllDeals(){
val intent = Intent(context,ViewAllDealsActivity::class.java)
intent.putExtra("details",deals)
Log.d("Tag2", "Size is ${deals.size}")
startActivity(intent)
}
I have the following function to get the data from the firestore and then I save the result in the variable 'deals'. However, whenever I click the 'tvAllDeals' it shows many images, when I check the size of the 'deals' using Log.d 'Tag1' always shows the correct size, which is 3, whereas 'Tag2' show some random numbers like 6, 9, 24. I try to find out why this is happening but I didn't get any idea. The variable 'deals' is not used anywhere else other than declaring and initializing, to assign the value and to pass it in the 'viewAllDeals()'
private fun getDeals() {
FirestoreClass().getDeals(
onSuccess = { list ->
Result.success(list)
successDeals(list) ///// THIS FUNCTION WILL SHOW THE IMAGES IN A VIEWPAGER
deals.clear()
deals=list
Log.d("Tag1", "Size is ${deals.size}")
},
onFailure = {
}
)
}
Edit:
NOTE: 'Tag3' also shows correct array size like 'Tag1'. However,
private fun successDeals(list: ArrayList<Deals>) {
Log.d("Tag3", "Size is ${deals.size}")
if (list.size > 0) {
binding.vpDeals.visibility = View.VISIBLE
val adapter = DealsAdapter(binding.vpDeals,requireContext(), list)
binding.vpDeals.adapter = adapter
binding.vpDeals.orientation = ViewPager2.ORIENTATION_HORIZONTAL
sliderHandle= Handler()
sliderRun= Runnable {
binding.vpDeals.currentItem=binding.vpDeals.currentItem+1
}
binding.vpDeals.registerOnPageChangeCallback(
object :ViewPager2.OnPageChangeCallback(){
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
sliderHandle.removeCallbacks(sliderRun)
sliderHandle.postDelayed(sliderRun,4000)
}
}
)
} else {
binding.vpDeals.visibility = View.GONE
}
}
Here is my Adapter class code:
class SearchPeopleAdapter(user: FirestoreRecyclerOptions<User>) :
FirestoreRecyclerAdapter<User, SearchPeopleAdapter.ViewHolder>(user) {
private var mUser : FirestoreRecyclerOptions<User>? = null
private var mOptions: FirestoreRecyclerOptions<User>? = null
private var mSnapshots: ObservableSnapshotArray<User>? = null
init {
mUser = user
}
fun firestoreRecyclerAdapter(user: FirestoreRecyclerOptions<User>?) {
mOptions = user
mSnapshots = user!!.snapshots
if (mOptions!!.owner != null) {
mOptions!!.owner!!.lifecycle.addObserver(this)
}
}
override fun startListening() {
if (!mSnapshots!!.isListening(this)) {
mSnapshots!!.addChangeEventListener(this);
}
}
override fun stopListening() {
mSnapshots!!.removeChangeEventListener(this)
notifyDataSetChanged()
}
//Inflate the xml
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.search_people_list_item, parent, false))
}
//Bind every dataView to the xml based on the Int value
override fun onBindViewHolder(viewHolder: ViewHolder, holderNumber: Int, user: User) {
viewHolder.apply {
itemView.search_people_person_list_name.text = user.Name
itemView.search_people_person_username.text = user.UserName
}
viewHolder.bind(user)
}
override fun getItemCount(): Int {
return mSnapshots!!.size
}
//Adds functionality to each View (aka ViewHolder) which is every person downloaded
inner class ViewHolder(itemView: View?) : RecyclerView.ViewHolder(itemView!!), View.OnClickListener {
//We are downloading User Objects so this Variable will be assigned to the UserObject downloaded
var currentUser : User? = null
//When the class is initiated this function is called which sets an OnClickListener to each View
init {
itemView!!.setOnClickListener(this)
}
//The Data from the Object is used to Populate the TextViews
fun bind(model: User) {
currentUser = model
itemView.search_people_person_list_name.text = model.Name
itemView.search_people_person_username.text = model.UserName
//If the user has set a profilePhoto then download & populate it with Glide
if ( model.ProfilePhotoChosen ) {
CompanionObjects.getPersonProfilePhotoStorageRef(model.Uid).downloadUrl.addOnSuccessListener {
val downloadUrl = it.toString()
Glide.with(itemView)
.load(downloadUrl)
.into(itemView.search_people_list_profile_image)
}.addOnFailureListener {
Timber.i("unable to retrieve your profile photo")
}
}
//Clear the View with the Glide.with(View).clear() method as the view will be reused and the photo
//might also get reused unnecessarily
else {
Glide.with(itemView).clear(itemView.search_people_list_profile_image)
}
}
//The onClick function is called when the View is clicked, in this case we are starting
// an Intent with the Intent Extra of userId to the PersonProfile Activity
// which will check for the IntentExtras and Populate the elements
override fun onClick(v: View?) {
val userId = currentUser!!.Uid
Timber.i("The click is $userId")
val intent = Intent(v!!.context, PersonProfileActivity::class.java)
intent.putExtra(CompanionObjects.USER_ID_INTENT_EXTRA, userId)
v.context.startActivity(intent)
}
}
}
Here are 2 of the methods in the Activity class, Inspite of calling the onStart and onStop method, the itemCount method always returns 0
override fun onStop() {
adapter!!.stopListening()
super.onStop()
}
//Retrieves data from Firestore and assigns the retrieved data to the search People Adapter
private fun retrieveDataFromFirestore(searchQuery : String) {
mFirestore = FirebaseFirestore.getInstance()
//Assigns the Collection Name from which needs to be queried
//the where conditions ensure to query a userDocument whose userName starts with the query entered in the searchField
val userNameQuery = mFirestore.collection(CompanionObjects.USERS_COLLECTION_NAME)
.whereGreaterThanOrEqualTo("userName", searchQuery)
.whereLessThanOrEqualTo("userName", "$searchQuery\uF7FF")
//Assigns the query to the User Objects that are related to Firestore
users = FirestoreRecyclerOptions.Builder<User>()
.setQuery(userNameQuery, User::class.java)
.build()
//Assigns the Firestore queried data to the search People Adapter
adapter = SearchPeopleAdapter(users!!)
registerAdapterObserver()
adapter!!.firestoreRecyclerAdapter(users!!)
adapter!!.startListening()
search_people_list.setHasFixedSize(true)
search_people_list.hasFixedSize()
search_people_list.layoutManager = LinearLayoutManager(this)
search_people_list.adapter = adapter
persistSearchQueryString(searchQuery)
}
When I call getItemCount method in the Activity class. It always returns 0 even thou the adapter does hold Views. How do I retrieve the exact count in the Adapter
Please make sure mSnapshots = user!!.snapshots produces items.
In fun retrieveDataFromFirestore(searchQuery : String) after adding items in adapter!!.firestoreRecyclerAdapter(users!!), you are not notifying RecyclerView to update.
So, don't forget to call
adapter!!.notifyDataSetChanged();
Your code might looks like this:
private fun retrieveDataFromFirestore(searchQuery : String) {
mFirestore = FirebaseFirestore.getInstance()
//Assigns the Collection Name from which needs to be queried
//the where conditions ensure to query a userDocument whose userName starts with the query entered in the searchField
val userNameQuery = mFirestore.collection(CompanionObjects.USERS_COLLECTION_NAME)
.whereGreaterThanOrEqualTo("userName", searchQuery)
.whereLessThanOrEqualTo("userName", "$searchQuery\uF7FF")
//Assigns the query to the User Objects that are related to Firestore
users = FirestoreRecyclerOptions.Builder<User>()
.setQuery(userNameQuery, User::class.java)
.build()
//Assigns the Firestore queried data to the search People Adapter
adapter = SearchPeopleAdapter(users!!)
registerAdapterObserver()
adapter!!.firestoreRecyclerAdapter(users!!)
adapter!!.startListening()
adapter!!.notifyDataSetChanged(); //<------- Add this line---------
search_people_list.setHasFixedSize(true)
search_people_list.hasFixedSize()
search_people_list.layoutManager = LinearLayoutManager(this)
search_people_list.adapter = adapter
persistSearchQueryString(searchQuery)
}
Ok I solved this, Here is the code:
private fun retrieveDataFromFirestore(searchQuery : String) {
mFirestore = FirebaseFirestore.getInstance()
//Assigns the Collection Name from which needs to be queried
//the where conditions ensure to query a userDocument whose userName
starts with the query entered in the searchField
val userNameQuery = mFirestore.collection(CompanionObjects.USERS_COLLECTION_NAME)
.whereGreaterThanOrEqualTo(userName, searchQuery.toLowerCase())
.whereLessThanOrEqualTo(userName, "${searchQuery.toLowerCase()}\uF7FF")
//Assign the Query to the user variable
users = FirestoreRecyclerOptions.Builder<User>()
.setQuery(userNameQuery, User::class.java)
.build()
//Assigns the Firestore queried data to the search People Adapter
adapter = object : SearchPeopleAdapter(users!!) {
//Need to create a class Body because it is open and this gives
//option to override its onDataChanged method in the Activity rather than in its adapter class
override fun onDataChanged() {
if ( itemCount == 0 ) {
search_people_list.visibility = View.INVISIBLE
retrieving_progress.visibility = View.INVISIBLE
empty_search_users_text.visibility = View.VISIBLE
//If was not searching with name field then prompt search with name field
if ( !searchingWithNameField ) {
//Code to add formatting options to the text like underline it
val content = SpannableString(getString(R.string.search_with_name_instead))
content.setSpan(UnderlineSpan(), 0, content.length, 0)
change_search_field_text.text = content
} else
//If was not searching with username field then prompt search with username field
{
//Code to add formatting options to the text like underline it
val content = SpannableString(getString(R.string.search_with_username_instead))
content.setSpan(UnderlineSpan(), 0, content.length, 0)
change_search_field_text.text = content
}
change_search_field_text.visibility = View.VISIBLE
} else {
//The adapter count is not 0 so show the recyclerView and hide the progress bar, emptyText, Change Search field Text etc.
search_people_list.visibility = View.VISIBLE
retrieving_progress.visibility = View.INVISIBLE
empty_search_users_text.visibility = View.INVISIBLE
change_search_field_text.visibility = View.INVISIBLE
//Should persist only if there is a result from the query ofCourse
persistSearchQueryStringAndSearchField(searchQuery)
}
}
}
adapterCreated = true
adapter!!.startListening()
adapter!!.notifyDataSetChanged()
//Make the progress bar visible and invisible soon as a document is added to the adapter
registerAdapterObserver()
search_people_list.setHasFixedSize(true)
search_people_list.hasFixedSize()
search_people_list.layoutManager = LinearLayoutManager(this)
search_people_list.adapter = adapter
}
So, What I did was made the adapter class open, then created an instance of the Adapter in the Activity and called the OnDataChanged() method in the Activity which watches for the itemCount in the Adapter. This way I am able to retrieve the correct the adapterCount value.
I want to display information that RecyclerView have no items, but I can't check if Firestore collection is empty. How to set some kind of listener which check if RecyclerView have items or not?
I'm assuming you're using Firebase UI (otherwise you would already have a query callback to hook into). In your FirestoreRecyclerAdapter, you can override onDataChanged & onError:
typealias DataChangedListener = (count: Int) -> Unit
typealias ErrorListener = (error: FirebaseFirestoreException) -> Unit
class MyAdapter(
options: FirestoreRecyclerOptions<MyModel>,
private val onDataChangedListener: DataChangedListener = {},
private val onErrorListener: ErrorListener = {}
) : FirestoreRecyclerAdapter<MyModel, MyViewHolder>(options) {
...
// Notify Activity/Fragment/ViewModel
override fun onDataChanged() =
onDataChangedListener.invoke(itemCount)
// Notify Activity/Fragment/ViewModel
override fun onError(e: FirebaseFirestoreException) =
onErrorListener.invoke(e)
}
You can use it like this:
recyclerView.adapter = MyAdapter(
options,
{ count -> showHideNoData(count > 0) },
{ error -> showError(error) }
)
...
fun showHideNoData(haveData: Boolean) {
recyclerView.isVisible = haveData
noDataView.isVisible = !haveData
errorView.isVisible = false
}
fun showError(error: FirebaseFirestoreException) {
recyclerView.isVisible = false
noDataView.isVisible = false
errorView.isVisible = true
// Logging & other UI changes
}
If it will be useful here is my solution. I simply called this function in the fragment where RecyclerView lives:
private fun setUpRecyclerView() {
val viewManagerPortrait = LinearLayoutManager(activity)
val viewManagerLandscape = GridLayoutManager(activity, 3)
val query = docRef.orderBy("title", Query.Direction.ASCENDING)
query.addSnapshotListener { p0, _ ->
if (p0 != null) {
if(p0.size() > 0) {
emptyAds.visibility = View.GONE;
listItems.visibility = View.VISIBLE
}else {
emptyAds.visibility = View.VISIBLE;
listItems.visibility = View.GONE
}
}
}
val options = FirestoreRecyclerOptions.Builder<Item>()
.setQuery(query,Item::class.java)
.setLifecycleOwner(this)
.build()
mAdapter = ItemCardsAdapter(this,options)
listItems.apply {
setHasFixedSize(true)
// use a linear layout manager if portrait, grid one else
layoutManager = if(activity!!.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE)
viewManagerLandscape
else
viewManagerPortrait
adapter = mAdapter
}
}
As you can see the if statement (inside the SnapShotListener) on size checks whether the database at that reference is empty, showing a message in the layout instead of the RecyclerView.
I make an ear training app and want the levels to be customizable. So I have a class with the same function for each of the 12 tones, so imagine setDb, setD, setEb etc.:
class MakeLevel(context: Context) {
fun setC(something: Boolean): Boolean {
var c = something
return c
}
I then instantiate the class in my main activity (FullscreenActivity):
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_fullscreen)
makeLevel = MakeLevel(this)
}
companion object {
lateinit var makeLevel: MakeLevel
}
Then in the fragment where the levels are selected, I do this:
override fun onResume() {
super.onResume()
majpentlevelbutton.setOnClickListener { view ->
FullscreenActivity.makeLevel.setC(true)
// [same for setD, setE, setG and setA, and false for all the other notes]
view.findNavController().navigate(R.id.action_levelSelectFragment_to_chromaticFragment)
}
}
Now here comes my problem: I want to access the value of c to determine whether ther sounds and the button for c should be loaded or not, and I can´t find a way to do so. For example, I´d like to use it like this:
if (c == true) {
c_button.visibility = View.VISIBLE
}
else {
c_button.visibility = View.GONE
}
I´ve tried c, makeLevel.c, FullscreenActivity.makeLevel.c and many more. Every time I get an Unresolved reference. So my question is how do I get a reference on the var c?
So far c is only a local variable within the method setC.
If you need the value outside of the method you need to define a property:
class MakeLevel(context: Context) {
var c = initValue
fun setC(something: Boolean){
c = something
}
}
Now you can access this variable with: FullscreenActivity.makeLevel.c
Your problem is that you are trying to access a variable outside of its scope.
class MakeLevel(context: Context) {
private var c = initValue
fun setC(something: Boolean){
c = something
}
fun getC(something: Boolean) {
return c
}
if (getC() == true)
c_button.visibility = View.VISIBLE
else
c_button.visibility = View.GONE
}