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.
Related
I'm updating my knowledge of Android, and I want to make an app with a Drawer menu, call an api and display the values inside a fragment. Starting from the template created by android studio itself, I have followed this tutorial:https://howtodoandroid.com/mvvm-retrofit-recyclerview-kotlin/ but I have a problem when programming the MainActivity.
Android studio template create this fragment (only changes the name of fragments):
class CheckListFragment : Fragment() {
private var _binding: FragmentCheckListBinding? = 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 {
val checklistViewModel =
ViewModelProvider(this).get(CheckListViewModel::class.java)
_binding = FragmentCheckListBinding.inflate(inflater, container, false)
val root: View = binding.root
val textView: TextView = binding.textChecklist
checklistViewModel.text.observe(viewLifecycleOwner) {
textView.text = it
}
return root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
The issue is that I don't know how I should call the viewmodel in that section, since I've tried in different ways, reading and reading examples, but none is exactly what I need. If you need me to show more parts of the code (viewmodel, viewmodelfactory etc. I can add it without any problem, although they are practically the same as those described in the tutorial)
In that example, in the MainActivity class we have this:
class MainActivity : AppCompatActivity() {
private val TAG = "MainActivity"
private lateinit var binding: ActivityMainBinding
lateinit var viewModel: MainViewModel
private val retrofitService = RetrofitService.getInstance()
val adapter = MainAdapter()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
viewModel = ViewModelProvider(this, MyViewModelFactory(MainRepository(retrofitService))).get(MainViewModel::class.java)
binding.recyclerview.adapter = adapter
viewModel.movieList.observe(this, Observer {
Log.d(TAG, "onCreate: $it")
adapter.setMovieList(it)
})
viewModel.errorMessage.observe(this, Observer {
})
viewModel.getAllMovies()
}
But here not use fragments.
I would greatly appreciate help, or a link where I can see an example of this
Is there a more generic way to init this two initialization lines?
private var _binding: MyFragmentViewBinding? = null
private val binding get() = _binding!!
Should we call every time binding
binding.cancelButton.setOnClickListener { }
binding.homeButton.setOnClickListener { }
binding.aboutButton.setOnClickListener { }
Or to create class variable?
cancelButton = binding.cancelButton
binding.cancelButton.setOnClickListener{}
And, should we set binding = null in adapter?
I think it is more of a personal preference. I like to do it with extension and higher-order function
fun <T : ViewDataBinding> Fragment.getDataBinding(layout: Int, container: ViewGroup?): T {
val binding: T = DataBindingUtil.inflate(layoutInflater, layout, container, false)
binding.lifecycleOwner = viewLifecycleOwner
return binding
}
My Fragment looks like this.
class InviteFragment : Fragment() {
private lateinit var binding: FragmentInviteBinding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = getDataBinding(R.layout.fragment_invite, container)
return binding.apply {
inviteAll.setOnClickListener(onInviteAllClick)
// You can set as many click listener here
// or some initialization related to view such as
// setting up recycler view adapter and decorators
}.root
}
private val onInviteAllClick = View.OnClickListener {
// Invite users
}
}
By doing things like this your onCreateView will be more readable and never going to get very long.
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.
I am currently working on my first Android app using Kotlin. In my activity are a lot of UI elements which I use to show dynamic information (see example below). For performance reasons I learned:
"Define a variable in the class and initialize it in the onCreate()
method."
This is kind of messy and my question is: are there other techniques to fulfill the same task but have a cleaner code? The variables are used in other methods later.
class MainActivity : AppCompatActivity() {
private lateinit var text_1: TextView
private lateinit var text_2: TextView
private lateinit var text_3: TextView
private lateinit var text_4: TextView
[...]
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
text_1 = findViewById(R.id.text1)
text_2 = findViewById(R.id.text2)
text_3 = findViewById(R.id.text3)
text_4 = findViewById(R.id.text4)
[...]
}
From ViewBinding official docs:
View binding is a feature that allows you to more easily write code that interacts with views
First, enable ViewBinding in your module:
android {
...
buildFeatures {
viewBinding true
}
}
Then, if you're calling views from activity, you should:
private lateinit var binding: ResultProfileBinding
override fun onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
binding = ResultProfileBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
}
and then you use binding instance to call the views:
binding.name.text = viewModel.name
binding.button.setOnClickListener { viewModel.userClicked() }
If you are calling views from a fragment, you should do it like following to avoid leaks:
private var _binding: ResultProfileBinding? = 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 = ResultProfileBinding.inflate(inflater, container, false)
val view = binding.root
return view
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
In Kotlin you just need to use the id directly without binding. The class will import this:
import kotlinx.android.synthetic.main.<your_layout_xml>.*
In this case it will import: kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
text_1.text = "Text1"
text_2.text = "Text2"
[...]
}
I have a basic knowledge of Kotlin. I'm working on a small application. Since I need a variable in more than one function in my application, I just created a variable under the class and gave a default value to this variable. However, this variable should only work when the application is first opened. Is there a code where I can do this?
class Main : Fragment() {
var numara = 0 //the variable I am talking about
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_main, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
zikirCek.text = "${numara}"
zikirCek.setOnClickListener {
zikir(it)
}
sifirlaButton.setOnClickListener {
sifirla(it)
}
kaydetButton.setOnClickListener {
kaydetme(it)
}
kayitYukle.setOnClickListener {
kayityukle(it)
}
try {
arguments?.let {
//numara = mainArgs.fromBundle(it).numara.toInt()
var secilenId = MainArgs.fromBundle(it).id
context?.let {
val db = it.openOrCreateDatabase("Zikirler", Context.MODE_PRIVATE, null)
val cursor = db.rawQuery(
"SELECT * FROM zikirler WHERE id = ?",
arrayOf(secilenId.toString())
)
val zikirr = cursor.getColumnIndex("zikir")
while (cursor.moveToNext()) {
zikirCek.setText(cursor.getString(zikirr))
}
cursor.close()
}
}
} catch (e: Exception){
e.printStackTrace()
}
}
These are also the functions I mentioned
fun zikir(view: View){
numara = numara + 1
zikirCek.text = "${numara}"
}
fun sifirla(view: View){
numara = 0
zikirCek.text = "${numara}"
}
fun kaydetme(view: View){
val action = MainDirections.actionMainToKaydet(numara)
Navigation.findNavController(view).navigate(action)
}
fun kayityukle(view: View){
val actionn = MainDirections.actionMainToKayityukleme()
Navigation.findNavController(view).navigate(actionn)
}
}
You can store the variable using SharedPreferences in Android:
// Prepare a SharedPreference Object.
val sharedPreferences = getSharedPreferences("preferences", Context.MODE_PRIVATE)
// To save the variable
sharedPreferences.edit().putInt("numara", numara).apply()
// To retrieve the variable
val numara = sharedPreferences.getInt("numara", 0)
To understand the answer better, I recommend you read this:
Interface for accessing and modifying preference data returned by Context#getSharedPreferences. For any particular set of preferences, there is a single instance of this class that all clients share. Modifications to the preferences must go through an Editor object to ensure the preference values remain in a consistent state and control when they are committed to storage.