i am working on comment section of an application like play store using mvvm,coroutine,kodein and DataBinding.i set login page in my main activity After the user presses the login button Comment Activity will become apparent.
I set recyclerview in comment activity. at end of every item i asked a question that was this review helpful or not. if not press no else press yes.i used shared preferences to save button state. for example when i clicked on yes button yesclicked(boolean variable) will be saved in sharedpref. till here everything works fine.
the problem--> i saved that state then i getYesButtonState() in onBindViewHolder method of recyclerview class and i said when activity recreated change the background color of yes button to #D5FFD7 for this purpose that you clicked on this before
but nothing happend and it didnt work
this code is for shared pref
val SPP_NAME = "ButtonState"
var buttonLocalState: SharedPreferences = context.getSharedPreferences(SPP_NAME, Context.MODE_PRIVATE)
fun setYesButtonState(isClicked: Boolean) {
val userLocalDatabaseEditor: SharedPreferences.Editor = buttonLocalState.edit()
userLocalDatabaseEditor.putBoolean("yesClicked", isClicked)
userLocalDatabaseEditor.apply()
}
fun setNoButtonState(isClicked: Boolean) {
val userLocalDatabaseEditor: SharedPreferences.Editor = buttonLocalState.edit()
userLocalDatabaseEditor.putBoolean("noClicked", isClicked)
userLocalDatabaseEditor.apply()
}
fun getYesButtonState(): Boolean? {
if (buttonLocalState.getBoolean("yesClicked", false) == false) {
return null
} else {
return true
}
}
fun getNoButtonState(): Boolean? {
if (buttonLocalState.getBoolean("noClicked", false) == false) {
return null
} else {
return true
}
}
fun clearButtonState() {
val userLocalDatabaseEditor: SharedPreferences.Editor = buttonLocalState.edit()
userLocalDatabaseEditor.clear()
userLocalDatabaseEditor.apply()
}
this is for recyclerview class:
//yes button clicked
yesbtn.setOnClickListener {
yesClicked = true
localStore.clearButtonState()
localStore.setYesButtonState(yesClicked)
val Helpful = 1
if (localStore.getYesButtonState() == true) {
nobtn.setBackgroundColor(Color.WHITE)
startColorAnimation(yesbtn)
activity.handler.postDelayed({
yesbtn.setBackgroundColor(Color.parseColor("#D5FFD7"))
nobtn.isClickable = true
}, 892.25.toLong())
}
try {
viewModel.deleteperson(
localStore.getUserName().toString(),
currentItem.id
).observe(mlifecycleOwner, Observer {
})
} catch (e: IllegalStateException) {
e.fillInStackTrace()
}
viewModel.feedback(
currentItem.id,
localStore.getUserName().toString(),
currentItem.description,
Helpful
).observe(mlifecycleOwner, Observer {
})
Toast.makeText(
context,
"thanks for your feedback",
Toast.LENGTH_SHORT
).show()
yesbtn.isClickable = false
}
//no button clicked
nobtn.setOnClickListener {
noClicked = true
localStore.clearButtonState()
localStore.setNoButtonState(noClicked)
val Helpful = 0
if (localStore.getNoButtonState() == true) {
yesbtn.setBackgroundColor(Color.WHITE)
startColorAnimation(nobtn)
activity.handler.postDelayed({
nobtn.setBackgroundColor(Color.parseColor("#D5FFD7"))
yesbtn.isClickable = true
}, 892.25.toLong())
}
try {
viewModel.deleteperson(
localStore.getUserName().toString(),
currentItem.id
).observe(mlifecycleOwner, Observer {
})
} catch (e: IllegalStateException) {
e.fillInStackTrace()
}
viewModel.feedback(
currentItem.id,
localStore.getUserName().toString(),
currentItem.description,
Helpful
).observe(mlifecycleOwner, Observer {
})
Toast.makeText(
context,
"please tell us why",
Toast.LENGTH_SHORT
).show()
it.isClickable = false
}
where i did wrong. thanks for your Help
try this
after the button click .. call notifyDataSetChanged(); in your adapter class
Related
How can I handle press on volume button up for 5 seconds in this override function
override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
val action = event!!.action
return when (val keyCode = event.keyCode) {
//handle press on volume up button
KeyEvent.KEYCODE_VOLUME_UP -> {
true
}
else -> super.dispatchKeyEvent(event)
}
}
I suggest using a delay on ACTION_DOWN and after the delay do your operation.
private var volumeUpJob : Job? = null
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
if (event.keyCode == KEYCODE_VOLUME_UP) {
if (event.action == ACTION_DOWN) {
if (volumeUpJob?.isActive != true) {
volumeUpJob = lifecycleScope.launch {
delay(5_000)
println("pressed for 5 sec")
// do your operation here
volumeUpJob?.cancel()
}
}
} else {
volumeUpJob?.cancel()
}
}
return super.dispatchKeyEvent(event)
}
Note that you need to cancel the job when user pull their finger up of the button. and also when we caught the 5 seconds as well.
i am translating an app in Kotlin and the user have the choice to choose between two different languages i created new Activity:
#Suppress("DEPRECATION")
class Languages_Activity : AppCompatActivity() {
lateinit var spinner: Spinner
lateinit var locale: Locale
var back_btn: LinearLayout? = null
private var currentLanguage = "en"
private var currentLang: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_languages_)
title = "KotlinApp"
currentLanguage = intent.getStringExtra(currentLang).toString()
spinner = findViewById(R.id.spinner)
val list = ArrayList<String>()
list.add("Select Language")
list.add("English")
list.add("Malay")
val adapter = ArrayAdapter(this, R.layout.support_simple_spinner_dropdown_item, list)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinner.adapter = adapter
spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
when (position) {
0 -> {
}
1 -> setLocale("en")
2 -> setLocale("my")
}
}
override fun onNothingSelected(parent: AdapterView<*>) {}
}
back_btn = findViewById(R.id.back_btn_language)
back_btn?.setOnClickListener {
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
}
}
private fun setLocale(localeName: String) {
if (localeName != currentLanguage) {
locale = Locale(localeName)
val res = resources
val dm = res.displayMetrics
val conf = res.configuration
conf.locale = locale
res.updateConfiguration(conf, dm)
val refresh = Intent(
this,
Languages_Activity::class.java
)
refresh.putExtra(currentLang, localeName)
startActivity(refresh)
} else {
Toast.makeText(
this#Languages_Activity, "Language, , already, , selected)!", Toast.LENGTH_SHORT).show();
}
}
override fun onBackPressed() {
val intent = Intent(Intent.ACTION_MAIN)
intent.addCategory(Intent.CATEGORY_HOME)
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
startActivity(intent)
finish()
exitProcess(0)
}
}
and when i choose a language the app will display the values of the chosen language but when i close the app and run it again it the choice will reset
how can i keep the item selected even after closing the app and run it again?
you can use shared preferences
for this usecase.
shared preferences are helpful when you want to store key-value pairs that can persist data across app close and open.
this data will get deleted only when you clear your app data in settings or uninstall the app
in your onCreate you can add this snippet
val sharedPref = activity?.getPreferences(Context.MODE_PRIVATE)
val selectedLanguageIndex = sharedPref?.getInt("LANGUAGE_SELECTED", 0)?:0
spinner.setSelection(selectedLanguageIndex)
in your onItemSelected
val sharedPref = requireActivity().getPreferences(Context.MODE_PRIVATE)
with (sharedPref.edit()) {
putInt("LANGUAGE_SELECTED", position)
apply()
}
kotlin has sharedpreference system.
it can help when u have to remember some options until change.
I have a button to delete all data on my activity screen (a chart, database data and a textview) and to display a toast.
Pressing the button does everything apart from getting rid of the textview, which only happens upon a second press. The code is in onCreate. Why is this happening and how can I fix it? Thank you :)
findViewById<ImageButton>(R.id.delete_btn).setOnClickListener {
pieChart.visibility = View.GONE
textView.visibility = View.GONE
appsViewModel.removeAll()
Toast.makeText(this, "Successfully deleted all", Toast.LENGTH_SHORT).show()
}
As #mayurgajra made me realise, some other code interfered with the changes in the textView. I managed to fix this with a boolean. I set it to true within the ImageButton event, and now I only modify the textview visibility in my other code if the boolean is false.
private var deleted: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
...
findViewById<ImageButton>(R.id.delete_btn).setOnClickListener {
pieChart.visibility = View.GONE
totalUsage.visibility = View.GONE
deleted = true
appsViewModel.removeAll()
Toast.makeText(this, "Successfully deleted all", Toast.LENGTH_SHORT).show()
}
}
private fun otherMethod(){
...
if (!deleted) {
totalUsage.text = "Total usage today: $hour"
totalUsage.visibility = View.VISIBLE
}
}
I am new to Firebase and RecyclerViews and I am trying to get a list of ingredients from my Firebase Database and I want to create checkboxes for every item I retrieve from the database. I got as far as creating a RecyclerView and adapter for my Firebase Query but binding lists from the query doesn't seem to be covered anywhere I could find.
Please find below the code for putting the data into the recycler view as well as the layout files being used for the recycler view.
Data being retrieved
that I want to display the original field of my object as checkboxes
ShoppingListFragment.kt
class ShoppingListFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//Enable Firestore logging
FirebaseFirestore.setLoggingEnabled(true)
//Firestore
firestore = Firebase.firestore
// Get recipes that the user has liked up to ${LIMIT}
query = firestore.collection("recipes").whereArrayContains(
"plannedBy",
Firebase.auth.uid.toString()
)
.orderBy("name", Query.Direction.ASCENDING)
.limit(LIMIT.toLong())
// Init the adapter to hold the recipe objects.
adapter = object : ShoppingListAdapter(query) {
override fun onDataChanged() {
if (itemCount == 0) {
shoppingListRecycler.visibility = View.GONE
viewEmpty.visibility = View.VISIBLE
} else {
shoppingListRecycler.visibility = View.VISIBLE
viewEmpty.visibility = View.GONE
}
}
override fun onError(e: FirebaseFirestoreException) {
// Show a snackbar on errors
view?.let {
Snackbar.make(
it,
"Error: check logs for info.", Snackbar.LENGTH_LONG
).show()
}
}
}
...
}
...
}
ShoppingListAdapter.kt
open class ShoppingListAdapter(query: Query) :
FirestoreAdapter<ShoppingListViewHolder>(query) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ShoppingListViewHolder {
return ShoppingListViewHolder(
ItemShoppingListBinding.inflate(
LayoutInflater.from(parent.context), parent, false))
}
override fun onBindViewHolder(holder: ShoppingListViewHolder, position: Int) {
holder.bind(getSnapshot(position))
}
}
ShoppingListViewHolder.kt
class ShoppingListViewHolder(val binding: ItemShoppingListBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(snapshot: DocumentSnapshot) {
val recipe = snapshot.toObject<Recipe>() ?: return
binding.listTitle.text = recipe.name
if(recipe.ingredients.size > 1) {
binding.shoppingListItem.text = recipe.ingredients[0].original
for(i in 1 until recipe.ingredients.size) {
val checkBox = CheckBox() //Not sure what to put here as the context for my recyclerview List Item
checkBox.text = recipe.ingredients[i].original
checkBox.layoutParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT)
binding.shoppingListContainer.addView(checkBox)
}
} else {
binding.shoppingListItem.text = recipe.ingredients[0].original
}
}
}
I have tried passing a context parameter through ShoppingListViewHolder.kt and in turn adding that parameter to the adapter and so forth like so.
class ShoppingListViewHolder(val binding: ItemShoppingListBinding, val context: Context) : RecyclerView.ViewHolder(binding.root) {
fun bind(snapshot: DocumentSnapshot) {
val recipe = snapshot.toObject<Recipe>() ?: return
binding.listTitle.text = recipe.name
if(recipe.ingredients.size > 1) {
binding.shoppingListItem.text = recipe.ingredients[0].original
for(i in 1 until recipe.ingredients.size) {
val checkBox = CheckBox(context)
...
}
ShoppingListFragment.kt
// Init the adapter to hold the recipe objects.
adapter = object : ShoppingListAdapter(query, requireContext()) {
override fun onDataChanged() {
if (itemCount == 0) {
shoppingListRecycler.visibility = View.GONE
viewEmpty.visibility = View.VISIBLE
} else {
shoppingListRecycler.visibility = View.VISIBLE
viewEmpty.visibility = View.GONE
}
}
override fun onError(e: FirebaseFirestoreException) {
// Show a snackbar on errors
view?.let {
Snackbar.make(
it,
"Error: check logs for info.", Snackbar.LENGTH_LONG
).show()
}
}
}
But all that did was create a huge amount of white space between entries. After some debugging, it looks like passing the context from the fragment gave it the MainActivity context. I'm not too sure where to go from here and any help would be appreciated. Thank you!
you can use binding.root.context except passing context
class LoginActivity : AppCompatActivity() {
private val firebaseAuth = FirebaseAuth.getInstance()
private val firebaseAuthListener = FirebaseAuth.AuthStateListener {
val user = firebaseAuth.currentUser?.uid
user?.let {
startActivity(HomeActivity.newIntent(this))
finish()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
loginProgressLayout.setOnTouchListener { v, event -> true }
}
fun onLogin(v: View) {
var proceed = true
if (emailET.text.isNullOrEmpty()) {
emailTIL.error = "email is required"
emailTIL.isErrorEnabled = true
proceed = false
}
if(passwordET.text.isNullOrEmpty()) {
passwordTIL.error = "password is required"
passwordTIL.isErrorEnabled = true
proceed = false
}
if(proceed){
loginProgressLayout.visibility = View.VISIBLE
firebaseAuth.signInWithEmailAndPassword(emailET.text.toString(), passwordET.text.toString())
.addOnCompleteListener { task ->
if (!task.isSuccessful){
loginProgressLayout.visibility = View.GONE
Toast.makeText(this#LoginActivity, "LoginError", Toast.LENGTH_SHORT).show()
}
}
.addOnFailureListener { exception ->
exception.printStackTrace()
loginProgressLayout.visibility = View.GONE
}
}
} //onLogin end
I checked I got something authentication number from firebaseAuth.signInWithEmailAndPassword code line.
But my question is about the property FirebaseAuth.AuthStateListener, which doesn't work.
When I get authentication number and then I want the AuthStateListener to work!
I read the Firebase API, but it didn't work. How can I make FirebaseAuth.AuthStateListener work?
You need to call addAuthStateListener with your listener in order for it to work.
So for example in the onStart of your activity:
override fun onStart() {
super.onStart()
firebaseAuth!!.addAuthStateListener(this.firebaseAuthListener!!)
}
I recommend studying this answer (more): Android Studio (Kotlin) - User has to log back into app every time app is closed