I want to use this single function only, but instead of UserAPIService, there may be ProductAPIService on runtime based on what user is clicking. Is that possible?
Code:
#Provides
#Singleton
fun provideRetrofitService(retrofit: Retrofit): UserAPIService = retrofit.create(UserAPIService::class.java)
UserAPIService:
interface UserAPIService {
#GET(URLConstants.API_CUSTOMER_REVIEWS_BY_ID + "{customerId}")
suspend fun getReviewData(#Path("customerId") value: String): Response<ReviewResponse>
}
FLOW:
Activity/Fragment - Viewmodel - repo - remotedatasource
I want to take event by id but it returns unit.
Entity:
#Dao
interface EventDao {
#Query("SELECT * FROM event_table WHERE eventId= :myeventId")
fun getEventByEventId(myeventId: Int): LiveData<Event>
}
Repository:
class EventRepository(private val eventDao: EventDao){
fun getEventById(myeventId: Int): LiveData<Event>{
return eventDao.getEventByEventId(myeventId = myeventId)
}
}
Viewmodel:
class EventViewModel (application: Application): AndroidViewModel(application) {
private val readEventById = MutableLiveData<LiveData<Event>>()
fun getEventById(eventId: Int) {
viewModelScope.launch(Dispatchers.IO) {
readEventById.postValue(eventRepository.getEventById(eventId))
}
}
}
I am calling it on user fragment:
lifecycleScope.launch {
val event = eventViewModel.getEventById(currentUserId)
}
but it returns unit. How can i return event by userId?
In your ViewModel class, you should include a public LiveData<Event> value that returns the readEventById live data object:
val selectedEvent: LiveData<Event>
get() = readEventById
Then, in the user Fragment, you should instead add an Observer on eventViewModel.selectedEvent. When you call eventViewModel.getEventById(currentUserId), you don't worry about the result. Instead, the Observer will let you know the LiveData<Event> was updated and you can handle the value this way.
This is the proper approach since you're getting the data from the database asynchronously.
We can use withContext for returning values from a coroutine scope, it is defination from documentation : "Calls the specified suspending block with a given coroutine context, suspends until it completes, and returns the result." for details visit the documentation withContext
in your case you can use like this
suspend fun getEventById(eventId: Int): LiveData<Event> {
return withContext(Dispatchers.IO) {
eventRepository.getEventById(eventId)
}
}
My project is currently using the following SingleLiveEvent implementation.
class SingleLiveEvent<T>(
private val allowMultipleObservers: Boolean = false
) : MutableLiveData<T>() {
private val mPending = AtomicBoolean(false)
private val observers = mutableSetOf<Observer<in T>>()
#MainThread
override fun observe(
owner: LifecycleOwner,
observer: Observer<in T>
) {
if (!allowMultipleObservers && hasActiveObservers()) {
Timber.tag(TAG)
.w("Multiple observers registered but only one will be notified of changes.")
} else {
observers.add(observer)
}
// Observe the internal MutableLiveData
super.observe(owner, Observer { t ->
if (mPending.compareAndSet(true, false)) {
observers.forEach { observer ->
observer.onChanged(t)
}
}
})
}
...
When we do this inside of onResume or onCreateView
SingleLiveEvent.observe(viewLifecycleOwner) {
requireContext()
}
We sometimes receive a crash saying that the Context is null. However, shouldn't requireContext() always give us a valid Context value since it's attached to the viewLifecycleOwner?
Your implementation is flawed - it's possible to register multiple observers as long as they attach in inactive state (or existing observers became inactive), then observer wrapper you passed to super.observe will fail because you're forcing a call to onChanged for all observers disregarding their actual state.
You should replace hasActiveObservers() with hasObservers(). But personally I'd look into solution implementing MediatorLiveData since this implementation doesn't properly handle observers that enter destroyed state, leaving them in your observers list forever.
requireContext() does nothing special except for null-check comparing to getContext().
Answer from Google:
How should we fix that?
Firstly we should fix potentials bugs.
Find all places where fragment can be detached at some point of time
Change code like in the sample below
In Java:
Context context = getContext();
if (context == null) {
// Handle null context scenario
} else {
// Old code with context
}
Source: https://medium.com/#shafran/fragment-getcontext-vs-requirecontext-ffc9157d6bbe
I have an single activity application with jetpack navigation, I need an object variable for all my application in many fragments. So I use a ViewModel, and I've created a Parent Fragment class which provide the ViewModel :
class MyViewModel : ViewModel() {
var myData : CustomClass? = null
...
}
open class ParentFragment : Fragment {
val model : MyViewModel by activityViewModels()
lateinit var myData : CustomClass
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
model.myData?.let {
myData = it
}
}
}
myDatashould not be null where I use ParentFragment, but sometimes, randomly I get kotlin.UninitializedPropertyAccessException: lateinit property myData has not been initialized when I use myData
Is it possible that my ViewModel doesn't keep myData? How can I be sure that my property has been initialized ?
UPDATE : Try 1
I've tried this code in my ParentFragment:
open class ParentFragment : Fragment {
val model : MyViewModel by activityViewModels()
lateinit var backingData : CustomClass
val myData : CustomClass
get() {
if (!::backingData.isInitialized)
model.getData()?.let {
backingData = it
}
return backingData
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
model.getData?.let {
backingData = it
}
}
}
But the problem doesn't disappear when I call myData, it seem's the ViewModelloses my data
UPDATE 2 : More code details
Before to go inside a fragment which extends ParentFragment, I set my data in ViewModel and then I navigate to the next fragment as below :
// Inside FirstFragment
if (myData != null) {
model.setData(myData)
findNavController().navigate(FirstFragmentDirections.actionFirstToNextFragment())
}
Is it possible that my NavController does navigation before the data was setted ?
EDIT 3 : Try to use custom Application class
According to an answer below, I've implemented a custom Application class, and I've tried to pass my object through this class :
class MyApplication: Application() {
companion object {
var myObject: CustomClass? = null
}
}
But unfortunately, there is no change for me. Maybe my object is too big to allocate correctly ?
Try this:
class MyViewModel : ViewModel() {
var myObject : CustomClass? = null
...
}
open class ParentFragment : Fragment {
lateinit var model : MyViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
model = ViewModelProvider(this).get(MyViewModel::class.java)
if(model.myObject == null) {
// initialize myObject, will be persisted by ViewModel
}
}
}
Note that MyViewModel and its member objects should not hold any references to Activity, Fragment, or Context, that include any indirect references to Context such as UI Views.
I will not recommend LiveData (or MutableLiveData) in your case, because a "feature" of LiveData is that their values are posted and updated asynchronously, hence call to observe may be too late.
Your wording hints some design flaws, namely:
You are refering to your data as object variable and to make it accessible at all times you chose to use a ViewModel. To me it sounds that you overthought your options.
Suggestion
Your object lifecycle appears to be managed manually by yourself. Therefore you should just use a static variable. This translates to Kotlin as a property within an (companion) object. I suggest you declare a custom Application class within your manifest and in its onCreate-method, you allocate your object and put it into the companion object of this class. Of course you can allocate it at any given time later on as well.
This will result in the following:
Access is always be possible via YourApplication.mData within your code.
Objects which relying on implementations outside the JVM can be managed properly.
For example: If you already bound to a port you won't be able to do this on a successive call - When the viewModel restores its state, for example. Maybe the underlying implementation did not report an error back to Java but allocating did not succeed. To manifest this assumption you would need to provide an description of your object variable. But As an famous example in the world of Android for this behaviour, try creating a soundPool via the SystemServices. You will experience lints about the correct usage of this object.
Deallocating can be done in the onTerminate() method of your
Application.class // edit_4: Doc of super.onTerminate() says the system will just kill your app. Therefore one needs to deallocate within an your activity. See code snippets below.
Clarification
The ViewModel of the JetPack Components is mainly responsible for saving and restoring the state of the view and binding to its model.
Meaning it handles the lifecycle across activities, fragments and possibly views. This is why you have to use an activity as the lifecycle owner in case you want to share an viewModel across multiple fragments. But I still suppose your object is more complex than just a POJO and my above suggestion results in your expected behaviour.
Also note that when multithreading, you shall not rely on the correct order of the lifecycle methods. There are only limited lifecycle-callbacks which are guaranteed to be called in a specific order by the android system, but the frequently used ones are unfortunately not included here. In this case, you should start processing at a more approrpiate time.
Even though the data should be similiar to the previous state, the exact reference depends on the hashCode implementation, but this is an JVM specific.
// edit:
ParentFragment is also bad naming, since you created a class which others shall inherit instead of refer to. If you want to access a specific variable within all your fragments, this needs to be implemented as an object (Singleton), since the Navigation component will prevent you from accessing the fragmentManager directly.
In plain android, one fragment can always refer to its parentFragment, iff this parentFragment has used its own childFragmentManager to commit the fragmentTransaction. Meaning also that fragments added by your Activity-fragmentManager have never an parentFragment.
// edit_2+3:
ViewModelProvider(activity!!, ViewModelFactory())[clazz]
is the correct call for creating and accessing a sharedViewModel:
The lifecycle owner needs to be the activity, otherwise after each fragmentTransaction done there will be a callback to the onCleared() method and the viewModel will release all references to avoid memory leaks.
// edit_4:
That your object was not correctly initialized was just an assumption which only would oocure if you tried to initialize it again. For example if you use an get()-method on an val where not appropriate.
Nonetheless, handling your object this way ensures that its lifecycle is outside your fragments. Here is an code example to clarify my wording:
// edit_5: To assert that the object reference is not damaged, include null checking (only if construction of CustomClass is non trivial)
Declare your CustomApplication
class CustomApplication : Application() {
companion object SharedInstances {
/**
* Reference to an object accessed in various places in your application.
*
* This property is initialized at a later point in time. In your case, once
* the user completed a required workflow in some fragment.
*
* #Transient shall indicate that the state could also be not Serializable/Parcelable
* This _could_ require manually releasing the object.
* Also prohibits passing via safeArgs
*/
#Transient var complex: CustomClass? = null
}
}
Intialization and Usage within your classes:
class InitializeComplexStateFragment: Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (complex != null) return#onViewCreated // prohibit successive initialization.
if (savedInstanceState != null) { /* The fragment was recreated but the object appears to be lost. */ }
// do your heavy lifting and initialize your data at any point.
CustomApplication.SharedInstances.complex = object : CustomClass() {
val data = "forExampleAnSessionToken"
/* other objects could need manual release / deallocation, like closing a fileDescriptor */
val cObject = File("someFileDescriptorToBindTo")
}
}
}
class SomeOtherFragment: Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
CustomApplication.SharedInstances.complex?.let {
// do processing
}
?: propagateErrorStateInFragment()
}
private fun propagateErrorStateInFragment() { throw NotImplementedError("stub") }
}
Deallocation if required
class SomeActivity: Activity() {
override fun onStop() {
super.onStop()
/* with multiple activities the effort increases */
CustomApplication.complex?.close()
}
}
You can check by using isInitialized on your property.
As the documentation says:
Returns true if this lateinit property has been assigned a value, and false otherwise.
You could initialize your property as null and do a null-check with the let as you already do though, no need to use lateinit and be careful with it, it is not a substitute for using a nullable var
You can use like this:
class MyViewModel : ViewModel() {
var mData: MutableLiveData<CustomClass>? = null
init {
mData = MutableLiveData<CustomClass>()
mData!!.value = CustomClass()
}
fun getData(): LiveData<CustomClass>? {
return mData
}
}
And your fragment :
open class ParentFragment : Fragment {
lateinit var model : MyViewModel
lateinit var myObject : CustomClass
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
model = ViewModelProvider(this).get(MyViewModel::class.java)
model.getData()?.observe(viewLifecycleOwner, Observer {
myObject = it
})
}
}
Ideally you should tie the sharedVM lifecycle to activity and then use the same sharedVM instance in all fragments. Also initialise the myObject in parentFragment/ activity class using setter(). Then get the object using getter().
sample code:
// SharedViewModel
var myObject : CustomClass? = null
fun setMyObject(obj : CustomClass?){
myObject = obj
}
fun getMyObject():CustomClass?{
return myObject
}
// Activity
val model: SharedViewModel by viewModels()
model.setMyObject(objectValue)
// ParentFragment
private val model: SharedViewModel by activityViewModels()
val obj = model.getMyObject()
Hope this helps you.Happy Coding :)
I'm currently designing database for a mobile application. Recently I found very useful function to access database in background:
private val IO_EXECUTOR = Executors.newSingleThreadExecutor()
fun ioThread(f : () -> Unit) {
IO_EXECUTOR.execute(f)
}
Besides that I figured out that don't need synchronization code as the database will be accessed only in one thread (i.e the thread used by SingleThreadExecutor).
The only issue is that the following methods have to be restricted to be invoked only through ioThread function (or using IO_EXECUTOR).
abstract class MyDatabase : RoomDatabase() {
companion object {
fun init(context: Context) { ... }
fun getInstance() { ... }
}
Is it possible to achieve this in Kotlin/Java?
UPDATE: for now I have this implementation but think there should be better ones
// App.kt file
private val IO_EXECUTOR = Executors.newSingleThreadExecutor()
private var IO_THREAD_ID: Long = -1L
private fun getIOThreadId(): Long {
if (IO_THREAD_ID == -1L)
IO_THREAD_ID = IO_EXECUTOR.submit(Callable<Long> { Thread.currentThread().id }).get()
return IO_THREAD_ID
}
fun notInIOThread() = Thread.currentThread().id != getIOThreadId()
fun ioThread(f : () -> Unit) {
IO_EXECUTOR.execute(f)
}
and then use notInIOThread() in init() and getInstance() functions
If you absolutely need to make sure that the code is running on the correct thread, you could make use of a custom thread and then checking Thread.currentThread() for the interface.
private interface MarkedIOThread // Marker interface
private val IO_EXECUTOR = Executors.newSingleThreadExecutor { r ->
return object : Thread(r), MarkedIOThread
}
fun notInIOThread(): Boolean = Thread.currentThread() !is MarkedIOThread
Yes, you can use android annotations`s Worker Thread annotation.
When you annotate a method or class with #WorkerThread, android will give you lint errors if you call it from the UI thread.
You can read more about the #WorkerThread here: https://developer.android.com/reference/android/support/annotation/WorkerThread
And more about android annotations here: https://developer.android.com/studio/write/annotations
I would suggest that you should check room library: https://developer.android.com/topic/libraries/architecture/room
It is very powerful, if you don't have any specific reason to create a database library, room is your best bet.
I assume you want the functions to be called only inside ioThread code block, otherwise there'd be a type error. First make them member functions of a class with user-code-inaccessible constructor so others cannot call it directly:
class MyDslClass internal constructor() {
fun init(context: Context) { ... }
fun getInstance() { ... }
}
And ioThread should be:
fun ioThread(f : MyDslClass.() -> Unit) {
val dsl = MyDslClass()
IO_EXECUTOR.execute { dsl.f() }
}
Then you can restrict calls to those functions only inside ioThread block.
fun main(args: Array<String>) {
ioThread {
getInstance() // Ok
}
// cannot call `getInstance` since I cannot construct a `MyDslClass`
}