So, basically I have a class:
class App : Application() {
lateinit var prefs: SharedPreferences
}
Now, I want to add a delegated property:
var isInitialized: Boolean by prefs.boolean()
The problem is that this, isInitialized property must be initialized lazily since I'm using Android Dagger2 framework, which performs injection after App creation (during calling onCreate() method):
class App : Application() {
lateinit var prefs: SharedPreferences
var isInitialized: Boolean = false
override fun onCreate() {
super.onCreate()
// how can I assign a delegate to isInitialized?
}
}
I would like it to be done either via:
lazy initialization during declaration (which is delegate in delegate - wondering whether this possible?)
lazy initialization during assignment
Is there any way of doing this?
Thanks!
You could do it with an indirection:
class DoubleDelegate<R, T>(var realDelegate: ReadWriteProperty<R, T> = /* some default */) : ReadWriteProperty<R, T> by realDelegate
then
val isInitializedDelegate = DoubleDelegate<App, Boolean>()
var isInitialized: Boolean by isInitializedDelegate
override fun onCreate() {
super.onCreate()
isInitializedDelegate.realDelegate = prefs.boolean()
}
Somehow I don't think this is actually a good idea.
Use Lazy
From the document Lazy Gets the lazily initialized value of the current Lazy instance. Once the value was initialized it must not change during the rest of lifetime of this Lazy instance.
Application class
val prefs: Prefs by lazy {
App.prefs!!
}
class App : Application() {
companion object {
var prefs: Prefs? = null
}
override fun onCreate() {
prefs = Prefs(applicationContext)
super.onCreate()
}
}
your data model class should be like this
class Prefs (context: Context) {
val PREFS_FILENAME = "com.teamtreehouse.colorsarefun.prefs"
val IsInitialized = "isInitialized"
val prefs: SharedPreferences = context.getSharedPreferences(PREFS_FILENAME, 0);
var initialized: Boolean
get() = prefs. getBoolean(IsInitialized, false)
set(value) = prefs.edit(). putBoolean(IsInitialized, value).apply()
}
then use Activity or fragment
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val initialized = prefs.initialized //getvalue
selectvalue(false)// set value
}
private fun selectvalue(value: Boolean) {
prefs.initialized = value
}
}
more details refer this example SharedPreferences Easy with Kotlin
Related
There are two classes MainActivity and PickTimeForNotif in my project. In MainActivity getSharedPreferences works just fine, i can save my data and get it back. In PickTimeForNotif, however, the same method seems to do nothing.
Here's my simplified MainActivity class:
class MainActivity : AppCompatActivity(), ChangeCupDialogFragment.StringListener {
#SuppressLint("SetTextI18n")
//this is variable i'm saving
private var drankToday = 0
//function in which i save my value to SharedPreferences
private fun saveWaterCountToInternalStorage(clickCounter: Int) {
val sharedPref = this.getSharedPreferences("something", Context.MODE_PRIVATE)
with (sharedPref.edit()){
putInt(getString(R.string.clickCount), clickCounter)
apply()
}
}
//and here i get it from there
private fun loadWaterCountToInternalStorage(): Int {
val sharedPref = this.getSharedPreferences("something", Context.MODE_PRIVATE)
return sharedPref.getInt(getString(R.string.clickCount), drankToday)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
val setupNotifButton = findViewById<Button>(R.id.setupNotifButton)
setupNotifButton.setOnClickListener{
val notifIntent = Intent(applicationContext, PickTimeForNotif::class.java)
startActivity(notifIntent)
}
}
}
In setOnClickListener i intend my second activity PickTimeForNotif, here it is.
class PickTimeForNotif: AppCompatActivity(), TimePickerFragment.OnCompleteListener {
val APP_PREFERENCES = "settings"
private val SAVED_FROM_HOUR = "SetFromHour"
private var FROM_HOUR = 99
private fun saveTimeToInternalStorage(prefName1: String, Hour:Int) {
val sharedPref = this.getSharedPreferences(APP_PREFERENCES, MODE_PRIVATE)
with (sharedPref.edit()){
putInt(prefName1, Hour)
apply()
}
}
private fun loadTimeFromInternalStorage() {
val sharedPref = this.getSharedPreferences(APP_PREFERENCES, MODE_PRIVATE)
if (sharedPref.contains(APP_PREFERENCES)) {
sharedPref.getInt(SAVED_FROM_HOUR, FROM_HOUR)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.pick_time_activity)
saveTimeToInternalStorage(SAVED_FROM_HOUR, 1)
loadTimeFromInternalStorage()
Toast.makeText(applicationContext,"$FROM_HOUR", Toast.LENGTH_SHORT).show()
}
}
In the code above i'm trying to set value (1 for example ) to a SAVED_FROM_HOUR key and then get it back and assign to FROM_HOUR variable. However, the Toast shows 99, which means that new data wasn't loaded properly. I tried putting all code from loadTimeFromInternalStorage and saveTimeToInternalStorage to onCreate, but the result is same.
I also tried checking if the Preferences file exists after i call getSharedPreferences with
if (sharedPref.contains(APP_PREFERENCES))
but it does not.
So i'm asking to explain what am i doing wrong and why i can save the data in my MainActivity, but not in the second one. Thanks alot to anyone in advance!!
In loadTimeFromInternalStorage(), you are fetching the value but not assigning to variable like this:
private fun loadTimeFromInternalStorage() {
val sharedPref = this.getSharedPreferences(APP_PREFERENCES, MODE_PRIVATE)
if (sharedPref.contains(APP_PREFERENCES)) {
FROM_HOUR = sharedPref.getInt(SAVED_FROM_HOUR, FROM_HOUR)
}
}
Also, in this line FROM_HOUR = sharedPref.getInt(SAVED_FROM_HOUR, FROM_HOUR), the last parameter in getInt() method is the default value so you should make another constant for it or supply it 0.
I have a MutableLiveData list in my repository as follows :
class AnswerRepository {
private var _answerList = mutableListOf<Answer>()
var answerList = MutableLiveData<MutableList<Answer>>()
fun addAnswerInList(answer: Answer) {
_answerList.add(answer)
answerList.value = _answerList
Log.e("AnswerRepository", "Answer List size : ${answerList.value?.size}")
Log.e("AnswerRepository", "_Answer List Size : ${_answerList.size}")
}
fun returnAnswerList(): MutableLiveData<MutableList<Answer>> {
return answerList
}
}
An item is added in 'answerList' (the MutableLiveData List) in a service as given below :
class FloatingWidgetService : Service(), View.OnClickListener{
private val answerRepository = AnswerRepository()
#SuppressLint("InflateParams")
override fun onCreate() {
super.onCreate()
//Getting the widget layout from xml using layout inflater
mFloatingView = LayoutInflater.from(this).inflate(R.layout.floating_widget, null)
initialiseViews()
setListeners()
}
#RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun onClick(p0: View?) {
when (p0?.id) {
R.id.next -> {
addItemInList()
}
}
private fun addItemInList(){
val answer = Answer(questionNumber, selectedOption, questionStatus)
answerRepository.addAnswerInList(answer)
}
Then this MutableLiveData List (answersList) is being observed in the fragment using viewmodel between repository and the fragment as follows :
ViewModel :
class SendAnswerToCloudViewModel : ViewModel() {
private val answerRepository = AnswerRepository()
val answerList = answerRepository.returnAnswerList()
}
Fragment :
class SendAnswerToCloud : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding: FragmentSendDataToCloudBinding = DataBindingUtil.inflate(
inflater,
R.layout.fragment_send_data_to_cloud,
container,
false
)
binding.lifecycleOwner = this
viewModel.answerList.observe(viewLifecycleOwner, Observer {
Log.e("SendAnswerToCloud", "isChangeTrigerred")
val answer = viewModel.answerList.value?.last()
Log.e(
"SendAnswerToCloud",
"QuestionNumber : ${answer?.questionNumber}, SelectedOption : ${answer?.selectedOption}, QuestionStatus : ${answer?.questionStatus}"
)
})
return binding.root
}
}
The list is successfully updated in the repository when addAnswerInListis called in the service. However nothing happens in the fragment (as the logs don't appear in the logcat).
So, what am I doing wrong ? Any kind of help would be highly appreciated. Thanks in Advance!!
The problem in your implementation is that you instantiate two AnswerRepository objects instead of one. Thus, you get two var answerList = MutableLiveData<MutableList<Answer>>() instead of one. While your SendAnswerToCloud to cloud listening for changes on the first answerList your service edits the other answerList. That is the reason you do not see any changes.
Make sure you create only one AnswerRepository object.
In programming people use dependency injection and optionally in combination with singleton pattern. Sometimes you can get away using only singleton pattern, but this is a less flexible solution and not so easy to test.
Detailed reponse
So the problem occurs because you have first object instantiation in FloatingWidgetService class:
class FloatingWidgetService : Service(), View.OnClickListener{
private val answerRepository = AnswerRepository()
...
and the second instantiation in SendAnswerToCloudViewModel class:
class SendAnswerToCloudViewModel : ViewModel() {
private val answerRepository = AnswerRepository()
...
}
This way you create two absolutely separate objects. Each one of them occupies different address in memory, and all of the objects you create inside AnswerRepository are also different between these two instances.
Imagine placing those declarations one after the other like this:
class SendAnswerToCloudViewModel : ViewModel() {
private val answerRepository = AnswerRepository()
private val answerRepository_second = AnswerRepository()
...
}
If you later compare them by equals method or by == operator you will get result false because they are two different objects. Thus, if you set a new value to answerList of answerRepository subscribers of answerList stored in answerRepository_second will not receive any updates.
Solution
Add companion object and make the primary constructor private.
class AnswerRepository private constructor() {
companion object {
private var INSTANCE: AnswerRepository? = null
fun getInstance(): AnswerRepository {
if (INSTANCE == null) {
INSTANCE = AnswerRepository()
}
return INSTANCE!!
}
}
private var _answerList = mutableListOf<Answer>()
var answerList = MutableLiveData<MutableList<Answer>>()
fun addAnswerInList(answer: Answer) {
_answerList.add(answer)
answerList.value = _answerList
Log.e("AnswerRepository", "Answer List size : ${answerList.value?.size}")
Log.e("AnswerRepository", "_Answer List Size : ${_answerList.size}")
}
fun returnAnswerList(): MutableLiveData<MutableList<Answer>> {
return answerList
}
}
Now instead of writing declarations with constructor invocation:
private val answerRepository = AnswerRepository()
You will call getInstance() method to get AnswerRepository.
private val answerRepository = AnswerRepository.getInstance()
This pattern is called singleton. When you ensure that your program has only one instance of a specific class.
Strongly recommend you to complete Essentials and Kotlin maps here.
I am new at Kotlin and trying to implement MVP Architecture,
Currently I am having problem initializing/setting textview's value outside onCreate() method
here is my code
SplashActivity.kt
class SplashActivity : AppCompatActivity(), Splash.ViewInterface {
lateinit var appDetail: AppDetail
lateinit var textTitle: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
textTitle = findViewById(R.id.splash_txt_title) as TextView
AppSingleton.appContext = applicationContext
var splashPresentation = SplashPresentation(this)
splashPresentation.getAppDetailFromService()
}
override fun fetchAppDetailSuccessful(response: SplashServiceObject) {
AppSingleton.initializeAppDetal(Gson().fromJson(response.json_string, AppDetail::class.java))
this.appDetail = AppSingleton.appDetail
}
override fun fetchAppDetailFailed(errMsg: String) {
textTitle.text = errMsg
}
}
SplashPresenter.kt
class SplashPresentation(private val view: Splash.ViewInterface) : Splash.PresentationInterface {
fun getAppDetailFromService() {
var splashService = SplashService()
splashService.getAppDetailFromAssets(this)
}
override fun fetchAppDetailFromServiceSuccessful(response: SplashServiceObject) {
view.fetchAppDetailSuccessful(response)
}
override fun fetchAppDetailFromServiceFailed(errMsg: String) {
view.fetchAppDetailFailed(errMsg)
}
}
SplashService.kt
class SplashService {
fun getAppDetailFromAssets(splashPresentation: SplashPresentation) {
val json_filename = "appdetail.json"
var jsonResponse: JsonResponse = AppSingleton.commonUtils.fetchJsonFromAssets(json_filename, AppSingleton.appContext!!)
if (jsonResponse.json_status) {
var splashServiceObj = SplashServiceObject
splashServiceObj.json_string = jsonResponse.json_info
splashServiceObj.response_msg = "JSON Successful fetched."
splashPresentation.fetchAppDetailFromServiceSuccessful(splashServiceObj)
} else {
splashPresentation.fetchAppDetailFromServiceFailed(jsonResponse.json_info)
}
}
}
in my SplashActivity().onCreate(), I am calling a Presenter that access Service, then the Service return a value to Presenter,
Then Presenter, return value to my SplashActivity's View, one of the function is, fetchAppDetailFailed(errMsg)
when I run the app, it crashes, saying the "textaa" is not yet initialized.
back in Java exp, when the variable is already instantiated on onCreate(), you can call this variable anywhere within the activity.
Thanks in advance!
You cannot instantiate Activities on Android. They are instantiated by the OS, and the OS calls the lifecycle methods on it.
In an MVP pattern, the View and Presenter both reference each other. Since Activity (the View) is the entry point of the application, your Activity should instantiate the Presenter and pass a reference of itself to the Presenter so communication can go both ways.
Also, the reference to the activity in the Presenter should be specified as a ViewInterface, not an Activity, or you're kind of defeating the purpose of using MVP.
class SplashPresentation(private val view: Splash.ViewInterface) : Splash.PresentationInterface {
//... methods that call functions on view
}
class SplashActivity : AppCompatActivity(), Splash.ViewInterface {
private val presenter = SplashPresentation(this)
//...
}
I tried to follow this tutorial, but an error occured when I try to assign a value to my Sh.Preference (prefs.token = "sometoken"):
kotlin.UninitializedPropertyAccessException: lateinit property prefs has not been initialized
I don't understand where's the bug, I also checked this thread.
Here are my code snippets
Prefs.kt :
class Prefs(context: Context) {
private val PREFS_FILENAME = "com.example.myapp.prefs"
private val PREFS_TOKEN = "token"
private val prefs: SharedPreferences = context.getSharedPreferences(PREFS_FILENAME, 0)
var token: String?
get() = prefs.getString(PREFS_TOKEN, "")
set(value) = prefs.edit().putString(PREFS_TOKEN, value).apply()
}
App.kt :
val prefs: Prefs by lazy {
App.prefs
}
class App : Application() {
companion object {
lateinit var prefs: Prefs
}
override fun onCreate() {
prefs = Prefs(applicationContext)
super.onCreate()
}
}
prefs.token has a default value of "", so why the logs said that has not been initialized?
Ok, problem found... The code was alright, I just missed to add this line
android:name=".App"
in the tag
<application in my Android Manifest.
In my case, i just initialize SharedPreference in onCreate(), and all works
For example: MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AppPreferences.init(this) // added this
}
SharedPreferences object:
object AppPreferences {
private const val NAME = "SpinKotlin"
private const val MODE = Context.MODE_PRIVATE
private lateinit var preferences: SharedPreferences
// list of app specific preferences
private val IS_FIRST_RUN_PREF = Pair("is_first_run", false)
fun init(context: Context) {
preferences = context.getSharedPreferences(NAME, MODE)
}
/**
* SharedPreferences extension function, so we won't need to call edit() and apply()
* ourselves on every SharedPreferences operation.
*/
private inline fun SharedPreferences.edit(operation: (SharedPreferences.Editor) -> Unit) {
val editor = edit()
operation(editor)
editor.apply()
}
// getter and setter with Shared Preference
var temperature: Float
// custom getter to get a preference of a desired type, with a predefined default value
get() = preferences.getFloat("temp",1f )
// custom setter to save a preference back to preferences file
set(value) = preferences.edit {
it.putFloat("temp", value)
}
}
In my mind, a private function can only be accessed inside a class, but in my example the following code bindPreferenceSummaryToValue(findPreference("RestoreItem")), which is outside the companion object, is accessible. Why?
Code
class UIPreference : AppCompatPreferenceActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.layout_preference)
fragmentManager.beginTransaction().replace(R.id.content, MyPreferenceFragment()).commit()
setControl()
utility.setAD(adView)
}
class MyPreferenceFragment : PreferenceFragment() {
private lateinit var mContext: Context
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
addPreferencesFromResource(R.xml.mypreference)
mContext=this.activity
setPreference()
bindPreferenceSummaryToValue(findPreference("RestoreItem"))
}
}
companion object {
private val sBindPreferenceSummaryToValueListener = Preference.OnPreferenceChangeListener { preference, value ->
val stringValue = value.toString()
if (preference is ListPreference) {
val listPreference = preference
val index = listPreference.findIndexOfValue(stringValue)
preference.setSummary(
if (index >= 0)
listPreference.entries[index]
else
preference.summary)
}
true
}
private fun bindPreferenceSummaryToValue(preference: Preference) {
preference.onPreferenceChangeListener = sBindPreferenceSummaryToValueListener
sBindPreferenceSummaryToValueListener.onPreferenceChange(preference,
PreferenceManager
.getDefaultSharedPreferences(preference.context)
.getString(preference.key, ""))
}
}
}
To David Rawson
Thanks! But from https://kotlinlang.org/docs/reference/visibility-modifiers.html, it seems that "private means visible inside this class only (including all its members);" for Classes and Interfaces.
Would you please see the image?
To yole:
Thanks! I can't access a private member in the class MyB outside companion object by the following code.
class UIPreference {
companion object {
private val aa=1
}
class MyA {
private val bar: Int =UIPreference.aa //OK
}
}
class MyB {
private val bar: Int = UIPreference.aa //Failed
}
In Kotlin, companion objects work effectively as a replacement for Java's static members, and visibility rules for them work in the same way. Just like in Java you can call a private static method from an instance method of a class, in Kotlin you can call a private method of a companion object from a regular method of a class.
Your private code is still within your UIPreference class and is therefore visible in this class (only!). That code, however, cannot be accessed outside your UIPreference class.