SharedPreference won't apply change (in a fragment) - android

In my Activity there is a ViewPager2 that loads a fragment which shows texts and another fragment which intended to have options such as the size of texts which shows on the other fragment. To make it not have to set the option every time I chose to use SharedPreferences, but it won't take effects. Here is the code:
class Options : Fragment() {
lateinit var binding: OptionsLayoutBinding
private lateinit var mPreferences: SharedPreferences
val preferencesEditor: SharedPreferences.Editor get() = mPreferences.edit()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mPreferences = this.requireActivity().getSharedPreferences("pref", Context.MODE_PRIVATE)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = OptionsLayoutBinding.inflate(inflater, container, false)
return binding.root
}
override fun onResume() {
super.onResume()
binding.radioGroupTextSize.setOnCheckedChangeListener { group, checkedId ->
preferencesEditor.run{
when (checkedId) {
R.id.textSize_RB1 -> putInt("TXTSZ", 12)
R.id.textSize_RB2 -> putInt("TXTSZ", 14)
R.id.textSize_RB3 -> putInt("TXTSZ", 16)
R.id.textSize_RB4 -> putInt("TXTSZ", 18)
R.id.textSize_RB5 -> putInt("TXTSZ", 20)
}
}
}
override fun onPause() {
super.onPause()
preferencesEditor.apply()
Log.d("PREF", "TEXT SIZE SET TO ${mPreferences.getInt("TXTSZ", 12)}. ")
}
}
.. The Log.d() is there to make sure where the problem happens and the log only says it's 12. It seems SharedPreferences is not saving the value, tt also doesn't take any effects to the target fragment which is meant to display texts in changed size. I wondered if apply() is placed wrong so I tried putting it after every radio button behaviours, which didn't improve the situation at all.
There are many other values neeed to be saved, but working out this one means they would work too, so I simplified the code here.
Thanks for help in advance!

Solved the problem; it creates xml but didn't write anyting on it. I still don't know why it worked that way, perhaps(most likely) it's a spaghetti code, so I just put simple codes that confirms working on an activity. It won't in a fragment, and made a simple change to fix it.
lateinit var binding: OptionsLayoutBinding
var preferencesEditor: SharedPreferences? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
preferencesEditor = activity?.getSharedPreferences("pref", Context.MODE_PRIVATE)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = OptionsLayoutBinding.inflate(inflater, container, false)
return binding.root
}
and putting .apply() after every putInt(). Made a custom function to not repeat it.

Related

Setting setOnClickListener using bindings android kotlin

I have been doing a tutorial that is a bit out of date and uses synthetics rather than bindings. I am trying to use bindins. I am trying to set up a listener in a fragment (AddEditFragment.kt). It's using a callback to MainActivity.onSaveClicked.
In AddEditFragment I use an import for the binding
import com.funkytwig.tasktimer.databinding.FragmentAddEditBinding
I have a lateinit on the first line of the class defenition
class AddEditFragment : Fragment() {
private lateinit var binding: FragmentAddEditBinding
I am initializing the bunding in onActivityCreated and setting up the listner. I can use findViewById to get the ID
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val addEditSave = view?.findViewById(R.id.addEditSave) as Button
addEditSave.setOnClickListener { listener?.onSaveClicked() }
}
And this works fine but if I try to use the binding
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
binding.addEditSave.setOnClickListener { listener?.onSaveClicked() }
}
The code does not show any errrors but it does not seem to create the listner. I have a Log.d in the onSaveClicked callback function and when I use the first (findViewById) version of the function it works (it calles onSaveClicked) but with the second version (using bindings) onSaveClicked does not get called when I click the Button.
I Cant figre out why the second version does not work, I thought the two versions of onActivityCreated should do the same thing.
The interface in AddEditFragment.kt is
interface OnSaveClicked {
fun onSaveClicked()
}
In fragment you should add your view in onCreateView or in OnViewCreated not in onActivityCreated
Please refer link for more details.
private var _binding: FragmentAddEditBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentAddEditBinding.inflate(inflater, container, false)
val view = binding.root
binding.addEditSave.setOnClickListener { listener?.onSaveClicked() }
return view
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
OK, thanks for all the help. turned out I was doing the inflate wrong.
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
Log.d(TAG, "onCreateView")
binding = FragmentAddEditBinding.inflate(layoutInflater, container, false)
return binding.root
}
I was doing
binding = FragmentAddEditBinding.inflate(layoutInflater)
I missed out on the last 2 args as I was taking the code from the inflate when I am in an Activity, not a Fragment. I think it is to do with the layout effecticly being in the parent.

How to run code just one time in Fragment on Android

In my application I have some fragments and for show this fragments I want use NavigationComponent.
I have one problem. When click on BottomNavigationItems and change fragments, run again fragment code!
I want just run codes just for first time!
My codes (for one of fragments) :
#AndroidEntryPoint
class HomeNewFragment : Fragment(), HomeContracts.View {
//Binding
private lateinit var binding: FragmentHomeNewBinding
#Inject
lateinit var presenter: HomePresenter
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = FragmentHomeNewBinding.inflate(layoutInflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//InitViews
binding.apply {
//Call apis
presenter.callApi()
}
}
}
For example when click on items, every time call this code : presenter.callApi()
Or when go to DetailFragment and when click on back, again call presenter.callApi()
How can I fix it?
You should replace your presenter.callApi() with fun onFragmentCreated triggered in your Presenter(ViewModel).
All you need is a boolean var to check its first time or not cause your Presenter(ViewModel) attatched to the activity, not the fragment so it can store your state.
Presenter {
fun onFragmentCreated() {
if(firstTime) callApi()

viewBinding not making any changes inside fragment

My Goal
I am trying to access the widget that was created inside my fragment using viewBinding.
What I have done / Info about my app
The language I am using is kotlin.
I have already added the code below into gradle:
buildFeatures{
dataBinding = true
viewBinding = true
}
I have tested binding.aTextView.setText("Code working.") inside my main activity and it works.
What's the problem
I have tested the setText code inside activity and it works. The problem right now is the same code when I move into the fragment it wouldn't work. And I am sure that the code has been executed as I putted a toast above it and the toast executed successfully which mean it should have at least reached that point before but not sure due to what reason there wasn't any changes.
My mainActivity Code:
class MainProgramActivity : AppCompatActivity() {
lateinit var binding: ActivityMainProgramBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainProgramBinding.inflate(layoutInflater)
setContentView(binding.root)
replaceFragment(FragmentMainPage())
}
private fun replaceFragment(fragment: Fragment){
val fragmentManager = supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()
fragmentTransaction.replace(R.id.fragmentContainerView,fragment)
fragmentTransaction.commit()
}
}
My fragment code:
class FragmentMainPage : Fragment(R.layout.fragment_main_page) {
lateinit var binding: FragmentMainPageBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
Toast.makeText(getActivity(),"Text!",Toast.LENGTH_SHORT).show();
binding = FragmentMainPageBinding.inflate(layoutInflater)
binding.aTextView.setText("Code working") //<-- I want this code to make changes towards the textView
return super.onCreateView(inflater, container, savedInstanceState)
}
}
The aTextView itself is empty at the beginning, the expected result will be the aTextView to show "Code working".
I see two problems with your code. First, exactly what Michael pointed out. You're returning the super method when you should be returning the View you just created (binding.root). Second, you're currenly leaking your fragment. When you viewbind a fragment, you are supposed to set the variable to null in onDestroyView(), as per defined in the documentation.
class FragmentMainPage : Fragment(R.layout.fragment_main_page) {
private var _binding: FragmentMainPageBinding? = null
private val binding get() = _binding!! // non-null variable in order to avoid having safe calls everywhere
// create the view through binding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentMainPageBinding.inflate(layoutInflater, container, false)
return binding.root
}
// view already created, do whatever with it
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.aTextView.setText("Code working")
}
// clear the binding in order to avoid memory leaks
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

RuntimeException: preferences has not been initialised

I searched among many similar SharedPreferences questions, but couldn't apply to my project.
It's a RuntimeException which requires a SharedPreferences lateinit variable to be initialised.
Error Message:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.me.tabuild3/com.me.tabuild3.Tutorial}: kotlin.UninitializedPropertyAccessException: lateinit property mPref has not been initialized
(...)
Caused by: kotlin.UninitializedPropertyAccessException: lateinit property mPreferences has not been initialized
at com.me.tabuild3.Z3Preferences.<init>(Z3Preferences.kt:15)
at com.me.tabuild3.Tutorial.onCreate(Tutorial.kt:15)
This is Tutorial.kt, which triggers the error:
class Tutorial : AppCompatActivity() {
val binding by lazy {
B00TutorialBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.b00_tutorial)
val fragmentList = listOf(Z0A(), Z1B(), Z3Preferences()) // the 15th line is here
val adapter = Z99FragmentAdapter(this)
adapter.fragmentList = fragmentList
binding.tutorialPager.adapter = adapter
binding.tutorialPager.currentItem = 1
}
}
This activity loads three fragments, and the third one, Z3Preferences(), edits SharedPreferences to modify things(At least I plan so).
This is Z3Preferences, which holds actual(?) error:
class Z3Preferences : Fragment() {
lateinit var binding: F04PrefBinding
private lateinit var mPref: SharedPreferences // seems this thing has to be initialised
val preferencesEditor: SharedPreferences.Editor = mPref.edit() // same error message for this line when the upper solved(?)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = F04PrefBinding.inflate(inflater, container, false)
return binding.root
}
override fun onResume() {
super.onResume()
binding.radioGroupTextSize.setOnCheckedChangeListener { group, checkedId ->
when (checkedId) {
R.id.textSize_RB1 -> preferencesEditor.putInt("TXTSZ", 12)
R.id.textSize_RB2 -> preferencesEditor.putInt("TXTSZ", 14)
R.id.textSize_RB3 -> preferencesEditor.putInt("TXTSZ", 16)
R.id.textSize_RB4 -> preferencesEditor.putInt("TXTSZ", 18)
R.id.textSize_RB5 -> preferencesEditor.putInt("TXTSZ", 20)
}
}
binding.radioGroupTextSpeed.setOnCheckedChangeListener { group, checkedId ->
when (checkedId) {
R.id.textSpd_RB1 -> {
preferencesEditor.putInt("TXTSP", 160)
preferencesEditor.putBoolean("TXTAN", true)
}
R.id.textSpd_RB2 -> {
preferencesEditor.putInt("TXTSP", 120)
preferencesEditor.putBoolean("TXTAN", true)
}
R.id.textSpd_RB3 -> {
preferencesEditor.putInt("TXTSP", 80)
preferencesEditor.putBoolean("TXTAN", true)
}
R.id.textSpd_RB4 -> {
preferencesEditor.putInt("TXTSP", 40)
preferencesEditor.putBoolean("TXTAN", true)
}
R.id.textSpd_RB5 -> {
preferencesEditor.putInt("TXTSP", 40)
preferencesEditor.putBoolean("TXTAN", false)
}
}
}
}
override fun onPause() {
super.onPause()
preferencesEditor.apply()
}
}
So.. How do I initialise the lateinit var mPref? I'm totally lost at this point and similar questions have different app construction and I couldn't get the idea.
you need the context to init the sharedPreferences.
You can do that like this:
mPref = this.activity!!.getSharedPreferences("pref", Context.MODE_PRIVATE)
I would do this in the onCreate method of the fragment.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mPref = this.activity!!.getSharedPreferences("pref",Context.MODE_PRIVATE)
//other initializations ...
}
So.. How do I initialise the lateinit var mPref?
You set it to some valid value, usually as early as possible. On Android, this usually means doing so in onCreate or onCreateView. But at least before anything tries to access it. You can do this in your onCreateView:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
mPref = // get preferences in your prefered way
binding = F04PrefBinding.inflate(inflater, container, false)
return binding.root
}
it shoots the same error for the next line(I marked on the code in the post): "lateinit property mPref has not been initialized", but at the next line! How do I do this?
You are trying to use an unintialized lateinit variable immediately after declaring it:
private lateinit var mPref: SharedPreferences // Declared but not initialized
val preferencesEditor: SharedPreferences.Editor = mPref.edit() // Immediately attemps to use mPref which is not initialized (during constructor call)
You need to not try to initialize the editor immediately like that.
You can either make it a property, so it only tries to access mPref when accessed and not during constructor call, which assumes it'll be valid by the time you try to access the editor:
val preferencesEditor: SharedPreferences.Editor get() = mPref.edit()
Or, you could make the editor itself a lateinit property and initialize it later:
lateinit var preferencesEditor: SharedPreferences.Editor
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
mPref = // get preferences in your prefered way
preferencesEditor = mPref.edit()
binding = F04PrefBinding.inflate(inflater, container, false)
return binding.root
}
Please check the documentation for how lateinit and kotlin constructors work.

How to initialize context in fragment on Android kotlin

In my application i want show message when fragment has show.
I used viewPager and BottomNavBar for show 4 fragments!
I want when click on BottomNavBar items show fragment and i want when visibility fragment show message.
I write below codes :
class HomeRegisteredFragment : Fragment() {
lateinit var toolbarTile: TextView
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_home_registered, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//Initialize
activity?.let {
toolbarTile = it.findViewById(R.id.homePage_toolbarTitle)
}
//Set title
toolbarTile.text = resources.getString(R.string.registered)
context?.let { ContextCompat.getColor(it, R.color.blue_active) }?.let {
toolbarTile.setTextColor(it)
}
}
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
if (isVisibleToUser) {
Log.e("showFragLog", "Show")
context?.let { Toast.makeText(it, "Show", Toast.LENGTH_SHORT).show() }
}
}
}
In my above codes, when click on my BottomNavBar for show fragment, show me Log message but not show Toast message.
When click on another BottomNavBar items and again click on previous BottomNavBar item, then show Toast message.
I think in first time not initialize context in setUserVisibleHint method.
How can i initialize context for show Toast in every time?
I changed your codes with below codes :
class HomeRegisteredFragment : Fragment() {
lateinit var toolbarTile: TextView
lateinit var handler: Handler
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_home_registered, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//Initialize
activity?.let {
toolbarTile = it.findViewById(R.id.homePage_toolbarTitle)
}
}
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
if (isVisibleToUser) {
//Initialize
handler = Handler()
//Set delay
handler.postDelayed({
Toast.makeText(requireContext(),"Show",Toast.LENGTH_SHORT).show()
}, 10)
}
}
}
First you should use requireContext() instead of context() for avoid from memory leak.
For show Toast for every time, you can initialize handler in setUserVisibleHint , then after some delay run your code!
I hope help you
Storing context in a variable is a horrible practive and most of the times leads to memory leaks, use requireContext() this method was introduced in Support Library 27.1.0. Nowdays most likely you will have a newer version or even using androidx so there is no excuse for storing a context
If you are looking for application context to show the toast message, try the below way and see if it works. Also, initialize it onCreate method so you have the activity context at that point.
val appContext = context!!.applicationContext
O have a similar trouble here. I have one Activity with multiple Fragments, and I need a ListView to show some employes.
But when I call the Adapter class, I don't know how to pass the context variable:
binding.listviewCoordenacoes.isClickable = true
binding.listviewCoordenacoes.adapter = CoordenadorAdapter(requireContext().applicationContext as Activity, arrayListCoordenador)
binding.listviewCoordenacoes.setOnClickListener{}
In the examples in general, it works in Activities. If not possible, I will create an Activity and put it in that.

Categories

Resources