I am working on an Android Studio project where I am using a singleton class to keep track of data (I have already done research on the pros and cons of singleton objects, and I decided it was the best solution for my project). I am, however, running into a few problems that seem to point back to my singleton object, for which I have not been able to find any good solutions on StackOverflow or other developer forums.
The first error I'm getting happens where I call the singleton object in another class.
Note: I am not instantiating this singleton class before using it, because if I understand Kotlin singletons correctly, you don't have to.
for (item in items) {
with(item.device) {
if (name == "BLE_DEVICE") {
count++
Data.addresses.add(address) >>> This is where I call the singleton object <<<
}
}
}
The second error I get comes from my initialization of SharedPreferences in the singleton class.
var sharedPref: SharedPreferences? = MainActivity().getSharedPreferences("MySharedPreferencesFile", Context.MODE_PRIVATE)
The third error I get comes from calling this function from my singleton object.
fun saveSharedPreferences() {
for ((key, value) in names) {
if (sharedPref != null) {
if (!sharedPref?.contains(key)!!) {
sharedPref
?.edit()
?.putString(key, value)
?.apply()
}
}
}
}
FOR REFERENCE:
a. Here are the important lines from my stack trace...
2022-08-30 16:07:05.422 9946-9946/? E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.punchthrough.blestarterappandroid, PID: 9946
>>> java.lang.ExceptionInInitializerError
>>> at com.punchthrough.blestarterappandroid.ScanResultAdapter.getItemCount(ScanResultAdapter.kt:62)
...
...
>>> Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.SharedPreferences android.content.Context.getSharedPreferences(java.lang.String, int)' on a null object reference
>>> at android.content.ContextWrapper.getSharedPreferences(ContextWrapper.java:174)
>>> at com.punchthrough.blestarterappandroid.Data.<clinit>(Data.kt:35)
at com.punchthrough.blestarterappandroid.ScanResultAdapter.getItemCount(ScanResultAdapter.kt:62)
b. This is my singleton class used for tracking data.
object Data {
// Format: <Device Address, Name>
// Used for keeping a record of all the devices, keeping
// duplicate advertisements off the screen, and saving the
// user-inputted names to the MAC address
var names: MutableMap<String, String> = mutableMapOf()
// Format: <Device Address>
// Used for keeping a count of how many views should be populated
// in the RecyclerView
var addresses = mutableSetOf<String>()
// TODO: Fix this line to resolve an initialization error
var sharedPref: SharedPreferences? = MainActivity().getSharedPreferences("MySharedPreferencesFile", Context.MODE_PRIVATE)
fun saveSharedPreferences() {
for ((key, value) in names) {
if (sharedPref != null) {
if (!sharedPref?.contains(key)!!) {
sharedPref
?.edit()
?.putString(key, value)
?.apply()
}
}
}
}
}
Never instantiate an Activity. It simply won't work and cannot be used for any useful purpose. Activities are full of properties that are set up when the OS creates the Activity for you. If you instantiate it yourself, you have a dead object full of null properties that are not supposed to be null.
Kotlin's built-in singleton (object) is unsuitable for singletons that depend on something else because it has no constructor for you to call to initialize the dependencies.
In this case, your singleton would have to be dependent on a Context to be able to use shared preferences, so a Kotlin object is not suitable.
This is how you can create a singleton that needs a context:
class Data private constructor(val context: Context) {
companion object {
private var instance: Data? = null
fun getInstance(context: Context): Data {
return instance ?: synchronized(this) {
instance ?: Data(context.applicationContext).also { instance = it }
}
}
}
val names: MutableMap<String, String> = mutableMapOf()
val sharedPref: SharedPreferences = context.getSharedPreferences("MySharedPreferencesFile", Context.MODE_PRIVATE)
fun saveSharedPreferences() { // I simplified this function a bit
sharedPref.edit {
for ((key, value) in names) {
if (!sharedPref.contains(key)) {
putString(key, value)
}
}
}
}
}
And each time you use it, you would need to pass a Context to Data.getInstance.
By the way, I highly discourage combining var with MutableList or MutableSet. It invites mistakes because outside classes won't know whether they should swap out the collection for a new instance or mutate it in place when they want to make changes. And other classes cannot know whether it's safe to cache a copy of the list because it may or may not change out from under them based on something some other class is doing.
Really, I wouldn't recommend ever exposing a MutableCollection (MutableList or MutableSet) or a var read-only Collection publicly from any class. It leaves you open to many possible types of bugs when outside classes can change a collection that a class is using internally, or that is used by multiple classes. Instead, I would make the collections private and expose functions that indirectly modify them such as addName().
Related
maybe I phrased my question a little strange, but something became interesting to me.
Let's imagine that I have some Extension function:
fun Int.foo() {
TODO()
}
Suppose that I need to pass the context of the Fragment from which I call it to this function, in which case I would do it like this:
fun Int.foo(context: Context) {
TODO()
}
Here we are explicitly passing the Context of our Fragment to the function. However, I'm interested in the question - is it possible to somehow change this function so (or can it be called in some other way) so that I do not have to explicitly pass the Context?
I understand that I could do like this:
fun Fragment.foo() {
var context = this.context
}
...however, I need an Extension function just above Int, so this method is not suitable.
Are there any ways how this can be done?
I guess you're looking for context-dependent declarations that let you combine multiple receiver scopes:
context(Fragment)
fun Int.foo() {
check(context != null) // context is actually Fragments context
}
Keep in mind however this feature is still in experimental state so it requires opt in by adding -Xcontext-receivers to your compiler options.
The Int class is just a class for op with numbers
It doesn't make sense to contain a Context object
It is not possible to get context without passing it to the function
There are other ways, which is to create a static object for the application class
for example
class App : Application() {
companion object {
var app: App? = null
}
init {
app = this;
}
}
and then
fun Int.foo(){
val context=App.app
...
}
I'm not very clear about the best way to inject into a static methods helper class (lets say a Custom class).
I'm kinda new to Kotlin, and as I've learnt we can access a method statically in two ways:
Object class.
Class + companion object.
To start, I'm not sure which one is the most recommended one (if there is a best practice regarding this), but my "problem" arises when needing to inject dependencies into a static method class.
Let's go with a simple example:
I have a static methods class called AWUtils (not decided if it should be an object class or a class with companion object yet though, and this will most likely depend on the injection mechanism recommended) with the next method:
fun setAmpersand2Yellow(text2Replace: String, target: String): String {
return text2Replace.replace(
target, "<span style=\"color:" +
app.drawerFooterColor + ";\">" + target + "</span>"
)
}
Here, app is the instance of my AppSettings class which holds all app configuration so, as you see setAmpersand2Yellow needs AppSettings, and of course I would't pass it as a parameter by any means, so it's a AWUtils dependence.
Using AWUtils as a class with companion object for the static methods I cannot inject directly AppSettings into company object as far as I know (at least I cannot do constructor injection, let me know if I'm wrong) and if I inject into companion object parent class (AWUtils) constructor then I don't know how to access those dependences from the companion object itself (the child).
If I use fields injection in AWUtils as a class then it complains than lateinit field has not been initialised and I don't know how to deal with this, because as far as I know lateinit fields are initialised in onCreate, which does not exist in this kind of classes.
One other possibility is to use an object with fields and set the dependencies values from caller in a static way before calling the method, for example:
object AWUtils {
var app: AppSettings? = null
fun setAmpersand2Yellow(text2Replace: String, target: String): String {
return text2Replace.replace(
target, "<span style=\"color:" +
app.drawerFooterColor + ";\">" + target + "</span>"
)
}
}
#AndroidEntryPoint
class OtherClass
#Inject constructor(private val app: AppSettings) {
fun AnyFunction() {
var mystr = "whatever"
AWUtils.app = app
var yellowStr = AWUtils.setAmpersand2Yellow(myStr)
}
}
In the end, I'm not sure on how to supply dependencies to a static methods class and which form of "static" class should I choose.
Edit 1:
Apart from my ApSettings class, I need a context, like for example in this next isTablet method:
val isTablet: String
get() {
return ((context.resources.configuration.screenLayout
and Configuration.SCREENLAYOUT_SIZE_MASK)
>= Configuration.SCREENLAYOUT_SIZE_LARGE)
}
In the end, I need a context and my AppSettings (or any other custom classes) to be injected anyway in a class with static methods.
Edit 2:
I could do (from the activity):
AWUtils.context = this
AWUtils.app = app
var isTablet = AWUtils.isTablet
And it works, but rather to be in the need of assigning a value to two fields (or more) every time I need to call a static method, I would prefer the fields to be injected in any way.
That's what dependency injection is meant for, isn't it?
Edit 3: I'm starting to be fed up with Hilt, what is supposed would have been created to simplify our life, only makes our programming life much more complicated.
As you clarified in the comments, you want to have your utils class accessible in an easy way across your codebase, so this answer will focus on that and on your original questions.
I'm kinda new to Kotlin, and as I've learnt we can access a method statically in two ways: Object class or Class + companion object.
Kotlin does not have Java-style statics. One reasoning behind it was to encourage more maintainable coding practices. Static methods and static classes are also a nightmare for testing your code.
In Kotlin you would go with an object (but a class + companion object would work in the same way)
object AWUtils {
lateinit var appContext: Context
lateinit var appSettings: AppSettings
fun initialize(
appContext: Context,
appSettings: AppSettings,
// more dependencies go here
) {
this.appContext = appContext
this.appSettings = appSettings
// and initialize them here
}
val isTablet: Boolean
get() = ((appContext.resources.configuration.screenLayout
and Configuration.SCREENLAYOUT_SIZE_MASK)
>= Configuration.SCREENLAYOUT_SIZE_LARGE)
fun setAmpersand2Yellow(text2Replace: String, target: String): String {
return text2Replace.replace(
target, "<span style=\"color:" +
appSettings.drawerFooterColor + ";\">" + target + "</span>"
)
}
}
Since this object should be accessible across the whole application it should be initialized as soon as possible, so in Application.onCreate
#HiltAndroidApp
class Application : android.app.Application() {
// you can inject other application-wide dependencies here
// #Inject
// lateinit var someOtherDependency: SomeOtherDependency
override fun onCreate() {
super.onCreate()
// initialize the utils singleton object with dependencies
AWUtils.initialize(applicationContext, AppSettings())
}
Now anywhere in your app code you can use AWUtils and AppSettings
class OtherClass { // no need to inject AppSettings anymore
fun anyFunction() {
val mystr = "whatever"
val yellowStr = AWUtils.setAmpersand2Yellow(myStr)
// This also works
if (AWUtils.isTablet) {
// and this as well
val color = AWUtils.appSettings.drawerFooterColor
}
}
}
There is another way in Kotlin to write helper/util functions, called extension functions.
Your isTablet check might be written as an extension function like this
// This isTablet() can be called on any Configuration instance
// The this. part can also be omitted
fun Configuration.isTablet() = ((this.screenLayout
and Configuration.SCREENLAYOUT_SIZE_MASK)
>= Configuration.SCREENLAYOUT_SIZE_LARGE)
// This isTablet() can be called on any Resources instance
fun Resources.isTablet() = configuration.isTablet()
// This isTablet() can be called on any Context instance
fun Context.isTablet() = resources.isTablet()
With the above extension functions in place the implementation inside AWUtils would be simplified to
val isTablet: Boolean
get() = appContext.isTablet()
Inside (or on a reference of) any class that implements Context, such as Application, Activity, Service etc., you can then simply call isTablet()
class SomeActivity : Activity() {
fun someFunction() {
if (isTablet()) {
// ...
}
}
}
And elsewhere where Context or Resources are available in some way, you can simply call resources.isTablet()
class SomeFragment : Fragment() {
fun someFunction() {
if (resources.isTablet()) {
// ...
}
}
}
Edit 3: I'm starting to be fed up with Hilt, what is supposed would have been created to simplify our life, only makes our programming life much more complicated.
Yeah, Hilt is focusing on constructor injection and can only do field injection out-of-the-box in very limited cases, afaik only inside Android classes annotated with #AndroidEntryPoint and inside the class extending the Application class when annotated with #HiltAndroidApp.
Docs for #AndroidEntryPoint say
Marks an Android component class to be setup for injection with the standard Hilt Dagger Android components. Currently, this supports activities, fragments, views, services, and broadcast receivers.
If you feel that you need a lot of field injection, because you are working with "static"-like objects in Kotlin, consider using Koin instead of Hilt for your next project.
I'm new to coding in kotlin and want to implement an immutable class that represents a project with various fields inside.
The easiest way to do this is by using a data class and using the copy() method so that anytime one of the app user modifies a field it results in the backend in a call to the copy method with the modified field producing the new project.
My problem is that this way does not allow for prior checking of parameters (eg : limit string size of the owner, making sure the number of people added to the project is reasonable etc).
If this was java, I'd use a builder pattern but this seems to defeat the purpose of kotlin, and i've read articles that are positive to using builders in kotlin (https://www.baeldung.com/kotlin/builder-pattern)
and others that are completely against (https://code-held.com/2021/01/23/dont-use-builder-in-kotlin/).
I haven't found any way to "modify" the copy method and to add the parameter sanitization checks that are needed for each parameter. I would appreciate any "smooth" idea to implement this, if anybody has found it. The goal would also be to throw exeptions/sealed classes variables so that the app UI can tell the user what went wrong instead of a generic error message just mentioning that the project was not modified.
I agree with the second link. If you look at the comments on the Baeldung article, you'll see even they were convinced and pledged to revise the article.
You can throw exceptions in an init block but if these are exceptions that are not caused by programmer error, it would be more Kotlin-idiomatic to expose a single constructor-like function that returns a wrapper or just null for invalid input.
Examples:
data class Person(val name: String, val age: Int = 0) {
init {
if (age < 0) {
throw IllegalArgumentException("Age $age is less than 0.")
}
}
}
If you want to return a wrapper or nullable, a data class isn't suitable for preventing invalid input because the generated copy() function will always return a fully constructed object. Sadly, Kotlin does not support overriding the generated copy() function.
sealed class Result<T>
data class Success<T>(val value: T): Result<T>()
data class Failure<T>(val reason: String): Result<T>()
class Person private constructor(val name: String, val age: Int = 0) {
companion object {
fun build(name: String, age: Int = 0): Result<Person> {
return when {
age < 0 -> Failure("Age $age is less than 0.")
else -> Success(Person(name, age))
}
}
}
fun buildCopy(name: String = this.name, age: Int = this.age) = build(name, age)
}
I've been working with Realm for well over a year so I'm not new to the whole migration flow but this has me scratching my head for several days:
During a data migration to a new schema version I need to create some objects and insert them to the DB, and later connect them to another type of objects.
First I create a map of the dynamic objects so I could connect them to the second type later:
val generatedStoreVisitTypes = mutableMapOf<String, DynamicRealmObject>()
Then I create and use the dynamicObjects:
fun migrateToVersion19(realm: DynamicRealm) {
// an extension method I created which adds the field if it doesn’t exist already, impl at the bottom
realm.schema.getOrCreate<RealmMetadata>()
// an extension method I created which adds the field if it doesn’t exist already, impl at the bottom
.safeAddRealmListField(RealmMetadata::storeVisitTypes, realm.schema)
.transform { metadata ->
// I use the string name of the property here and not reflection since this field is deleted during this migration
val currentStoreTaskList = metadata.getList("storeTasks")
currentStoreTaskList.forEach { storeTasks ->
// create an instance here and initialise it
val visitTypeTasks = realm.createObject(MetaVisitTypeTasks::class.java.simpleName)
visitTypeTasks[MetaVisitTypeTasks::visitTypeId.name] = "1"
val visitTasks = visitTypeTasks.getList(MetaVisitTypeTasks::visitTasks.name)
storeTasks.getList("storeTasks").forEach {
visitTasks.add(it)
}
// save the object to the map
generatedStoreVisitTypes[storeUid] = visitTypeTasks
}
}
.safeRemoveField("storeTasks")
realm.schema.getOrCreate<RealmStore>()
.safeAddRealmListField(RealmStore::visitTypes, realm.schema)
.transform {
val storeUid = it.getString(RealmStore::storeUid.name)
// crash here on the “add” method
it.getList(RealmStore::visitTypes.name).add(generatedStoreVisitTypes[storeUid])
}
}
}
private inline fun <reified T> RealmSchema.getOrCreate(): RealmObjectSchema {
return get(T::class.java.simpleName) ?: create(T::class.java.simpleName)
}
private inline fun <reified TClass : RealmObject, reified TListItem : RealmObject, reified TList : RealmList<TListItem>> RealmObjectSchema.safeAddRealmListField(addedField: KMutableProperty1<TClass, TList>, schema: RealmSchema): RealmObjectSchema {
val fieldName = addedField.name
val listItemObjectSchema = schema.get(TListItem::class.java.simpleName)
if (!hasField(fieldName)) {
return addRealmListField(fieldName, listItemObjectSchema)
}
return this
}
Calling the "add" method in the second “transform” method sometimes causes a -
“java.lang.IllegalStateException: Object is no longer valid to operate on. Was it deleted by another thread?”
I’m familiar with this error and usually know how to handle it but I can’t recreate it or understand how such a thing happens in this case.
Since we’re talking about migration, there shouldn’t be another thread that’s operating on the same schema - as the execution is synchronised, isn’t it?.
Also, we’re talking about an object that was just created. There’s no other context in which it’s referenced or used.
I don’t understand how this object could be deleted. What could cause such an error?
I have migrated my application from Java to Kotlin. In Java, the copying was working just fine. However, when migrated to Kotline it was not working. After that, I came to know about copy method in Kotlin
I have tied this, but it seems I am doing something wrong.
Here is my function :
fun updateSwitchState(deviceName: String, switchNumber: Int): AuraSwitch? {
val singleDevice = Switch()
for (c in FourNodeDevice) {
if (deviceName == c.name) {
val copyDevice : SwitchClone = SwitchClone(c.state, c.name)
val state = copyDevice.copy(state = copyDevice.state)
state.updateState(switchNumber)
singleDevice.state = state.state
return singleDevice
}
}
return null
}
Whenever I change data in object state in updateState Method the value in object c also gets changed. Any help will be useful
You never create a copy of a state object.
This call creates another SwitchClone with values identical to copyDevice itself.
val state = copyDevice.copy(state = copyDevice.state)
copy() only creates a shallow copy, which means all of your objects, in that case c, copyDevice and state point to the same c.state.
You need to explicitly create a deep copy (depending on what properties are mutable) of state object and assign it to copyDevice.state field.
For Kotlin when using the Kotlin Data Class data class you get a function called copy() for you. But If your Class is not a Data Class and your project has Gson and you want to copy the whole object ( probably edit after getting it ), Then if all those conditions are true then this is a solution. This is also a DeepCopy. ( For a data Class you can use the function copy()).
Then if you are using Gson in your project. Add the function copy():
class YourClass () {
// Your class other stuffs here
fun copy(): YourClass { //Get another instance of YourClass with the values like this!
val json = Gson().toJson(this)
return Gson().fromJson(json, YourClass::class.java)
}
}
If you want to install Gson then get the latest version here.
The copy() did not solve my purpose. However clone() did. I added the following line in my code and it worked as I desired.
val state = c.states.clone()