Object in Kotlin loosing it var values? - android

I recently made the transition from iOS/Swift to Androi/Kotlin. I am using a object for encapsulating Analytics functionality (as I did in Swift):
Object Analytics{
var connection: AnalyticsConnection? = null
fun sendEvent(name: String)...
init {
connection = //allocate here
}
}
I heard (one of our Android devs mentioned it) that in the latest version of kotlin the var connection could actually loose it value at some point spontanically? I find this very weird, is this true? It seams that vars at global scope get cleared out at some point?

It should not lose the values it contains because when you create a new class as an object you create it as a thread-safe singleton.
Which means unless you directly change the value somewhere else, the value should stay the same as the original value.
That's all in case your application is still alive, in case your application is killed/ destroyed the whole class would also be destroyed with it.

Related

Lifetime of object declared in constructor (kotlin)

I am running calling a native function in Kotlin that takes a Unix file descriptor as a parameter. After the native function runs for a few minutes it report EBADF.
The code looks something like
class A(val file: ParcelFileDescriptor) : AutoCloseable {
private var fileDes: Int = -1
private external fun longRunningNativeFn(fd : Int) : FnResult
init {
fileDes = file.fd
}
fun process() : FnResult {
longRunningNativeFn(fileDes)
}
override fun close {
}
}
The file object passed into the constructor is not held anywhere else.
My working theory is that since file is only used in the init block, file then becomes a candidate for garbage collection so after a few minutes when the garbage collector kicks in, the file object calls close resulting in the native code getting a bad file descriptor.
So
is this theory correct?
If so what determines the lifetime of parameters in the constructor?
Does adding file.close() to the close function extend the lifetime of file for the duration of the class?
Note after adding the file.close(), I am no longer getting the BADF in my native code. Even though as #Ivo points out adding val to the primary constructor makes file a class member, the JRE might be smart enough to see nothing is using file and garbage collect it early since it needs to stick around until close is called
It's not merely a parameter of the constructor. You defined it as a property by writing val in front of it. You can access it throughout the entire lifetime of the instance. If you only want to have it as parameter you need to leave out the val like
class A(file: ParcelFileDescriptor) : AutoCloseable {
As for the lifetime if you write it like that, I personally don't know but I assume it gets garbage collected after the last init block but that's just a guess. And of course it also matters whether there are other references to that object outside of that class. I also have no idea why that EBADF happens

Changing Data Class From Live Data

I have a BaseViewModel that basically has the function to get the user data like so:
abstract class BaseViewModel(
private val repository: BaseRepository
) : ViewModel() {
private var _userResponse: MutableLiveData<Resource<UserResponse>> = MutableLiveData()
val userResponse: LiveData<Resource<UserResponse>> get() = _userResponse
fun getUserData() = viewModelScope.launch {
_userResponse.value = Resource.Loading
_userResponse.value = repository.getLoggedInUserData()
}
}
In my Fragment, I access this data by just calling viewModel.getUserData(). This works. However, I'd like to now be able to edit the data. For example, the data class of UserResponse looks like this:
data class UserResponse(
var id: Int,
var username: String,
var email: String
)
In other fragments, I'd like to edit username and email for example. How do I do access the UserResponse object and edit it? Is this a good way of doing things? The getUserData should be accessed everywhere and that is why I'm including it in the abstract BaseViewModel. Whenever the UserResponse is null, I do the following check:
if (viewModel.userResponse.value == null) {
viewModel.getUserData()
}
If you want to be able to edit the data in userResponse, really what you're talking about is changing the value it holds, right? The best way to do that is through the ViewModel itself:
abstract class BaseViewModel(
private val repository: BaseRepository
) : ViewModel() {
private var _userResponse: MutableLiveData<Resource<UserResponse>> = MutableLiveData()
val userResponse: LiveData<Resource<UserResponse>> get() = _userResponse
fun setUserResponse(response: UserResponse) {
_userResponse.value = response
}
...
}
This has a few advantages - first, the view model is responsible for holding and managing the data, and provides an interface for reading, observing, and updating it. Rather than having lots of places where the data is manipulated, those places just call this one function instead. That makes it a lot easier to change things later, if you need to - the code that calls the function might not need to change at all!
This also means that you can expand the update logic more easily, since it's all centralised in the VM. Need to write the new value to a SavedStateHandle, so it's not lost if the app goes to the background? Just throw that in the update function. Maybe persist it to a database? Throw that in. None of the callers need to know what's happening in there
The other advantage is you're actually setting a new value on the LiveData, which means your update behaviour is consistent and predictable. If the user response changes (either a whole new one, or a change to the current one) then everything observeing that LiveData sees the update, and can decide what to do with it. It's less brittle than this idea that one change to the current response is "new" and another change is "an update" and observers will only care about one of those and don't need to be notified of the other. Consistency in how changes are handled will avoid bugs being introduced later, and just make it easier to reason about what's going on
There's nothing stopping you from updating the properties of the object held in userResponse, just like there's nothing stopping you from holding a List in a LiveData, and adding elements to that list. Everything with a reference to that object will see the new data, but only if they look at it. The point of LiveData and the observer pattern is to push updates to observers, so they can react to changes (like, say, updating text displayed in a UI). If you change one of the vars in that data class, how are you going to make sure everything that needs to see those changes definitely sees them? How can you ensure that will always happen, as the app gets developed, possibly by other people? The observer pattern is about simplifying that logic - update happens, observers are notified, the end
If you are going to do things this way, then I'd still recommend putting an update function in your VM, and let that update the vars. You get the same benefits - centralising the logic, enabling things like persistence if it ever becomes necessary, etc. It could be as simple as
fun setUserResponse(response: UserResponse) {
_userResponse.value?.run {
id = response.id
username = response.username
email = response.email
}
}
and if you do decide to go with the full observer pattern for all changes later, everything is already calling the function the right way, no need for changes there. Or you could just make separate updateEmail(email: String) etc functions, whatever you want to do. But putting all that logic in the VM is a good idea, it's kinda what it's there for
Oh and you access that object through userResponse.value if you want to poke at it - but like I said, better to do that inside a function in the VM, keep that implementation detail, null-safety etc in one place, so callers don't need to mess with it
The ideal way to update userResponse you should change/edit _userResponse so that your userResponse we'll give you the updated data.
it should be something like this
_userResponse.value = Resource<UserResponse>()

How to ensure a global "cache" for the current session of the user even if process is killed

I will try to be as clear and precise as possible in my situation.
Currently developing an Android application with Koltin based on the MVVM pattern, I'm facing a problem that questions my architecture.
My application consists in retrieving the different orders of a user, each order having itself a list of products.
I had imagined setting up a "CommandController" which is actually a singleton.
object CommandController : ICommandController {
override var m_commandRepository: ICommandRepository = CommandRepository()
override var m_commandList: MutableList<Command> = mutableListOf<Command>()
override var m_currentCommand: Command? = null
override var m_currentProduct : Produit? = null
override var m_currentProductIndex : Int = first_index
}
The purpose of this singleton is to act as an intermediary between my viewModels and my repository in order to store the commands received and the currently active command (only 1 active command at a time).
class CommandListViewModel : ViewModel() {
fun fetchCommandList(refreshStrategy: CommandRefreshStrategy){
viewModelScope.launch {
mProgressBar.postValue(true)
mErrorStatus.postValue(null)
try {
mCommandList.postValue(CommandController.getCommandsList(refreshStrategy)) //USE CONTROLLER HERE
mCommandActive.postValue(CommandController.getCurrentCommand()) //USE CONTROLLER HERE
}
catch (e: Exception) {
//this is generic exception handling
//so inform user that something went wrong
mErrorStatus.postValue(ApiResponseError.DEFAULT)
}
finally {
mProgressBar.postValue(false)
}
}
}
}
Note that no element has a reference to the singleton, kotlin initializing it when it is first needed
If I click on the active command in the list, I display its details
I chose to do this to avoid having to remake queries every time I need to get the list of commands, no matter the fragment / activity, even if for the moment I only use it in 1 place.
So my layers are as follows:
A problem I wasn't aware of is annoying.
Indeed, if I change the permissions granted by the application and I come back in the application, the last activity launched is recreated at the last visited fragment.
The problem is that my singleton comes back to its initial state and so my active command is null because the process was killed by the system after the change of permissions.
So I would like to know if there is a way to persist/recover the state of my singleton when I come back into the application.
I've already tried to transfer my singleton to a class inherited from Application, but that doesn't solve the problem of the killed process.
I've read a lot of articles/subjects about shared preferences but I have a lot of problems with it:
The controller is supposed to be a purely business element, except shared preferences need the context
I don't want the current command and the list of commands to remain if the user kills the application himself ( is there a way to differentiate between the death of the process by the system and by the user ??).
Android will kill off OS processes for all kinds of reasons. If you want your data to survive this then you must store it in a persistent store. One of the following:
Store it in a file
Store it in SharedPreferences
Store it in an SQLite database
Store it in the cloud on some server where you can retrieve it later

How to listen some var from another class without reference in Kotlin

When I developed android app with Java I used EventBus , then I used java.util.observable to listen some var without getting a reference of a class/service.
For example, I could listen in ViewModel/Activity some var from a service without getting instance of that service.
I did it before I started using Clean Architecture, SOLID, MVVM etc.
Now I am using widely LiveData and ViewModel pattern, and Dependency Injection with Dagger2. So, every time I have a reference to listen a LiveData var.
I am wandering if exist in Kotlin a way to listen a var in one class from another class without getting an another class's reference?
This is not practical question, it's just curiosity
For listening to data from a variable just use LiveData.
wrap your variable in LiveData and use an observer to observe data changes to it.
Also if your variable is somewhere in a class which you don't want to create object of. Then you can simply wrap that variable in companion object like this.
class Test() {
companion object {
var testvariable = "Hello"
}
}
then simply call it like this "Test.testvariable"

Lifecycle methods in statically typed languages

In the last year I've become a mobile developer and a functional programming admirer.
In each of the mobile arenas there are components with lifecycle methods that make up the meat of the app. The following will use Android and Kotlin as examples, but the same applies to iOS and Swift.
In Android, there are Activity's with lifecycle methods like onCreate(). You might also define a function, onButtonClicked(), which will do exactly what the name describes.
For the purposes of the question, let's say there's a variable defined in onCreate() that is used in a button click handler onButtonClickedPrintMessageLength() (This is usually the case - onCreate() is essentially Activity's setup method).
The example class would look like this:
class ExampleActivity: Activity() {
var savedStateMessage: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
savedStateMessage = "Hello World!"
}
fun onButtonClickedPrintMessageLength() {
System.out.println(savedStateMessage?.length)
}
}
Notice the declaration of savedStateMessage as a String? (nullable string) and the use of ?. (null safe call). These are required because the compiler cant guarantee that onCreate() will be called before onButtonClickedPrintMessageLength(). As developers though, we know that onCreate will always be called first* **.
My question is how can I tell the compiler about the guaranteed order of these methods and eliminate the null checking behavior?
* I suppose it's possible to new up our ExampleActivity and call onButtonClickedPrintMessageLength() directly, thus sidestepping the Android framework and lifecycle methods, but the compiler/JVM would likely run into an error before anything interesting happened.
** The guarantee that onCreate is called first is provided by the Android framework, which is an external source of truth and might break/function differently in the future. Seeing that all Android apps are based on this source of truth though, I believe it's safe to trust.
Although this won't answer your actual question, in Kotlin you can use lateinit to tell the compiler that you'll initialize a var at a later point in time:
lateinit var savedStateMessage: String
You'll get a very specific UninitializedPropertyAccessException if you try to use this variable before initializing it. This feature is useful in use cases like JUnit, where you'd usually initialize variables in #Before-annotated method, and Android Activitys, where you don't have access to the constructor and initialize stuff in onCreate().
As mentioned in another answer, lateinit is available as an option to defer initialization to a later point in a guaranteed lifecycle. An alternative is to use a delegate:
var savedStateMessage: String by Delegates.notNull()
Which is equivalent, in that it will report an error if you access the variable before initializing it.
In Swift this is where you would use an implicitly-unwrapped Optional:
class Example: CustomStringConvertible {
var savedStateMessage: String! // implicitly-unwrapped Optional<String>
var description: String { return savedStateMessage }
init() {
savedStateMessage = "Hello World!"
}
}
print(Example()) // => "Hello World!\n"
By using the operator ! at the end of String in the second line of the example you are promising that the variable will be set before it can be used. This is accomplished in the init method of the example. It's still an Optional but code can treat it as a String since it will be automatically unwrapped before each use. You must take care that the variable is never set to nil when it might be accessed or a runtime exception may be generated.

Categories

Resources