OnRestoreInstanceState returning null after rotation - java

When i type something in listView and after that rotate it listView returns null instead of text i typed in. Can you please explain why is this happening.
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
super.onRestoreInstanceState(savedInstanceState)
Log.i("ToDoList","OnRestoreInstanceState was called")
val listSave = savedInstanceState?.getStringArrayList("list")
this.list.clear()
this.list.add(listSave.toString())
adapter = ArrayAdapter <String> (this, android.R.layout.simple_list_item_1,list)
toDoList.adapter = adapter
}
override fun onSaveInstanceState(outState: Bundle?, outPersistentState: PersistableBundle?) {
super.onSaveInstanceState(outState, outPersistentState)
Log.i("ToDoList","OnSaveInstanceState was called")
outState?.putStringArrayList("list", list)
}

Root cause: You override wrong onSaveInstanceState() method.
Solution: You must override onSaveInstanceState(Bundle) instead of onSaveInstanceState(Bundle, PersistableBundle).
Change your code from
override fun onSaveInstanceState(outState: Bundle?, outPersistentState: PersistableBundle?) {
super.onSaveInstanceState(outState, outPersistentState)
Log.i("ToDoList","OnSaveInstanceState was called")
outState?.putStringArrayList("list", list)
}
to
override fun onSaveInstanceState(outState: Bundle?) {
super.onSaveInstanceState(outState)
Log.i("ToDoList","OnSaveInstanceState was called")
outState?.putStringArrayList("list", list)
}

Related

When i click on multiple buttons, multiple audios start playing at the same time in android studio

When i click on multiple images of recyclerview then multiple audios start playing without stopping the previous one. It is getting irritating. I hope someone can help me fix this. I have read about singleton class and using only one instance of media player but couldn't understood how to implement it correctly.
class RecylerViewAdapter (var context: Context, var arrayList: ArrayList<ItemModel>) :
RecyclerView.Adapter<RecylerViewAdapter.ItemHolder>() {
var mediaPlayer: MediaPlayer? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemHolder {
val viewHolder = LayoutInflater.from(parent.context)
.inflate(R.layout.sound_item_list, parent, false)
return ItemHolder(viewHolder)
}
override fun onBindViewHolder(holder: ItemHolder, position: Int) {
val itemModel: ItemModel = arrayList[position]
holder.soundImage.setImageResource(itemModel.soundImage)
holder.soundTitle.text = itemModel.soundTitle
mediaPlayer = MediaPlayer.create(context, itemModel.soundTrack)
mediaPlayer!!.isLooping = true
holder.soundImage.setOnClickListener {
Toast.makeText(context, itemModel.soundTitle, Toast.LENGTH_LONG).show()
mediaPlayer!!.start()
}
}
override fun getItemCount(): Int {
return arrayList.size
}
class ItemHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var soundImage = itemView.soundImage
var soundTitle = itemView.soundTitle
}
}
Code seems to be good. Remember to call release() on your player and initialise it again with new media file before playing it. Maybe it could be useful for you to implement a MediaPlayer.OnCompletionListener in which you could call player.release()
Or maybe this is enough:
holder.soundImage.setOnClickListener {
mediaPlayer!!.release()
mediaPlayer = null
mediaPlayer = MediaPlayer.create(context, itemModel.soundTrack)
Toast.makeText(context, itemModel.soundTitle, Toast.LENGTH_LONG).show()
mediaPlayer!!.start()
}
I'll recommend to initialize just single object of MediaPlayer in your fragment or activity in which your RecyclerView is located and than pass that object into adapter as a parameter and use it and dont forget to release MediaPlayer in onDestroy() of activity or onDestroyView() of fragment by calling MediaPlayer?.release(). In adapter OnClickListener() you have to stop the media player calling MediaPlayer?.stop() and MediaPlayer?.reset() and than add the new source
class RecylerViewAdapter (var context: Context, var arrayList: ArrayList<ItemModel>, var mediaPlayer : MediaPlayer?) :
RecyclerView.Adapter<RecylerViewAdapter.ItemHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemHolder {
val viewHolder = LayoutInflater.from(parent.context)
.inflate(R.layout.sound_item_list, parent, false)
return ItemHolder(viewHolder)
}
override fun onBindViewHolder(holder: ItemHolder, position: Int) {
val itemModel: ItemModel = arrayList[position]
holder.soundImage.setImageResource(itemModel.soundImage)
holder.soundTitle.text = itemModel.soundTitle
holder.soundImage.setOnClickListener {
mediaPlayer?.stop()
mediaPlayer?.reset()
mediaPlayer?.setDataSource(itemModel.soundTrack)
mediaPlayer?.isLooping = true
mediaPlayer?.start()
}
}
override fun getItemCount(): Int {
return arrayList.size
}
class ItemHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var soundImage = itemView.soundImage
var soundTitle = itemView.soundTitle
}
}

How would I get the context of a RecyclerView List Item?

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

Android: Presist the UI State and Data in Android(Kotlin)

I have A Bottom Navigation Bar Using Navigation Components It Contain 3 Fragments let Say A(Home), B And C. When I Moved From B To C Fragment B Destroys And Similarly C To B C Destroys. When I Re-Select A It Again Makes Network Call Fetches The Data and Display. I Don't Want To Make A Network Request Again And Again When Fragment Destroys Or Re-Selected.
I'm Using ViewModel, Retrofit, LiveData, Coroutines And Kotlin.
Please Help Me To Fix This Problem
Base Fragment
abstract class BaseFragment<VM: ViewModel, VB: ViewBinding, R: BaseRepository>:Fragment() {
private var _viewBinding: VB? = null
protected val viewBinding
get() = _viewBinding!!
protected lateinit var viewModel: VM
protected lateinit var userTokenData: UserTokenData
protected lateinit var remoteDataStore: RemoteDataSource
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
remoteDataStore = RemoteDataSource()
userTokenData = UserTokenData(requireContext())
_viewBinding = getFragmentBinding(inflater, container)
val factory = ViewModelFactory(getRepository())
viewModel = ViewModelProvider(this, factory).get(getViewModelClass())
lifecycleScope.launch {
userTokenData.token.first()
}
return viewBinding.root
}
abstract fun getViewModelClass():Class<VM>
abstract fun getFragmentBinding(inflater: LayoutInflater, container: ViewGroup?): VB
abstract fun getRepository(): R
override fun onDestroyView() {
super.onDestroyView()
Log.d(MyTAG, "onDestroyView")
_viewBinding = null
}
Home Fragment
class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding, UserRepository>() {
override fun getViewModelClass() = HomeViewModel::class.java
override fun getFragmentBinding(
inflater: LayoutInflater,
container: ViewGroup?
) = FragmentHomeBinding.inflate(inflater, container, false)
override fun getRepository(): UserRepository {
val token = runBlocking { userTokenData.token.first() }
return UserRepository(remoteDataStore.buildApi(UserApi::class.java, token))
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewBinding.progressBar.visible(true)
viewModel.user.observe(viewLifecycleOwner, Observer {
when(it){
is Resources.Success -> {
updateUI(it.value.user)
}
is Resources.Loading -> {
viewBinding.progressBar.visible(true)
}
}
})
}
fun updateUI(user: User){
viewBinding.progressBar.visible(false)
viewBinding.email.text = user.email
viewBinding.name.text = user.name
}
}
In each Fragment, put this before onCreate
private val myViewModel: MyViewModel by activityViewModels()
Also, in order to do that, you will need to have this dependency:
implementation "androidx.fragment:fragment-ktx:1.2.5"

How to open a DialogFragment written in Kotlin class from a Java class

I created a WaterQualityChecksDialogFragment, please correct if I have not written it correctly.
WaterQualityChecksDialogFragment.kt
class WaterQualityChecksDialogFragment : DialogFragment() {
#Inject
private lateinit var app: App
fun newTargetInstance(): WaterQualityChecksDialogFragment {
val fragment = WaterQualityChecksDialogFragment()
return fragment
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
app!!.bus.post(ScreenDimEvent(false))
val builder = AlertDialog.Builder(activity!!.applicationContext)
// Get the layout inflater
val inflater = activity!!.layoutInflater
#SuppressLint("InflateParams")
val inflatedView = inflater.inflate(R.layout.dialog_water_quality_cheks, null)
builder
.setView(inflatedView)
.setCancelable(false)
/*
* setPositiveButton and setNeutralButton are also set (and overridden) in {#link #onStart}
* to stop Android automatically closing the dialog on click
*/
.setPositiveButton(R.string.fuel_order_signature_dialog_save) { dialog, id ->
}
.setNeutralButton(R.string.fuel_order_signature_dialog_clear) { dialog, id ->
}
val dialog = builder.create()
// Stop touch events outside the dialog from cancelling/dismissing
dialog.setCanceledOnTouchOutside(false)
return dialog
}
interface WaterQCChecksDialogListener {
fun onDialogPositiveClick(dialog: WaterQualityChecksDialogFragment)
fun onDialogNegativeClick(dialog: WaterQualityChecksDialogFragment)
}
}
ServiceOrderDialogHelper.java
public void showWaterQCChecksDioalog(){
//Cannot call newTargetInstance() method.
waterQualityChecksDialogFragment = WaterQualityChecksDialogFragment.newTargetInstance()
}
Put newTargetInstance() inside companion object to make this static like java
companion object {
fun newTargetInstance(): WaterQualityChecksDialogFragment {
val fragment = WaterQualityChecksDialogFragment()
return fragment
}
}
And call from java like below:
WaterQualityChecksDialogFragment.Companion.newTargetInstance();
Please check this code, it may helps you
class DialogClassSample : DialogFragment() {
companion object {
fun newInstance(): DialogClassSample {
val dialog = DialogClassSample()
return dialog
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
isCancelable = false
return inflater.inflate(R.layout.view, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setListener()
}
fun setListener() {
button.setOnClickListener({
dismiss()
})
}
override fun onResume() {
super.onResume()
val window = dialog?.window
val size = Point()
val display = window?.windowManager?.defaultDisplay
display?.getSize(size)
val width = size.x
window?.setLayout((width * 0.85).toInt(), WindowManager.LayoutParams.WRAP_CONTENT)
window?.setGravity(Gravity.CENTER)
}
}
and call fron your class
val dialog = DialogClassSample.newInstance()//show dialog description according to user type
dialog.show(activity?.supportFragmentManager?.beginTransaction(), DialogClassSample::class.java.name)
In on resume we have set dialog size dynamically ,because in some bigger size dialog its look not good so you can use as per your requiremnent

Send data from adapter to activity and getStringExtra() return null value in first clicking

I tried to send data from adapter A to Activity A.
In adapter A I used Intent.putExtra() and it's success.
And in Activity A I used getStringExtra() to get the data.
When I clicked an item , the getStringExtra() returns null value. But, when I don't close my app and click the same item again, I got the value.
This is Adapter A :
class AdapterA (private val daftarMeja: ArrayList<DaftarMeja.Meja>, private val clickListener: (DaftarMeja.Meja) -> Unit) : RecyclerView.Adapter<RecyclerView.ViewHolder>(){
companion object {
const val ID_MEJA = "idMeja"
const val STATUS_MEJA = "statusMeja"
}
override fun getItemCount(): Int {
return daftarMeja.size
}
fun updateData(dataBaru: ArrayList<DaftarMeja.Meja>) {
daftarMeja.clear()
daftarMeja.addAll(dataBaru)
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val cellForRow = layoutInflater.inflate(R.layout.item_meja,parent,false)
return PartViewHolder(cellForRow)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
(holder as PartViewHolder).bind(daftarMeja[position], clickListener)
}
inner class PartViewHolder (itemView: View): RecyclerView.ViewHolder(itemView){
fun bind(meja: DaftarMeja.Meja, clicklistener: (DaftarMeja.Meja) -> Unit){
itemView.tv_table_name.text = meja.nama
if(meja.status){
itemView.container_table.setBackgroundResource(R.color.colorTableAvailable)
itemView.tv_table_name.setTextColor(Color.BLACK)
}
else {
itemView.container_table.setBackgroundResource(R.drawable.gradient)
itemView.tv_table_name.setTextColor(Color.WHITE)
}
itemView.setOnClickListener {
val intent = Intent(itemView.context, ActivityA::class.java)
intent.putExtra(ID_MEJA, meja.id)
intent.putExtra(STATUS_MEJA, meja.status)
itemView.context.startActivity(intent)
clicklistener(meja)
}
}
}
}
To get the data in Activity A :
val idMeja = intent.getStringExtra(MejaAdapter.ID_MEJA)
When the item clicked :
private fun mejaItemClicked() {
val intent = Intent(this, ActivityB::class.java)
startActivity(intent)
}
I don't know where is the problem. Please help me to solve this
1)In Activity onCreate() , this is the right mode to test Bundle
Bundle extras = getIntent().getExtras();
if (extras != null)
{
if (extras.containsKey("YOURNAMEPARAM"))
{....your assign}
}
2)Wend lanch a intent FIRST you put in extra of intent the value
val intent = Intent(this, ActivityB::class.java)
intent.putExtra("YOURNAMEPARAM",yourvalue);
startActivity(intent)
3)Make sure that the type of variable you are passing is consistent and use the "PUT" and the "GET"corresponding to the variable.
4)Make sure the activity is not already open, otherwise the onCreate () method will not be called and your getextra will not be re-evaluated.

Categories

Resources