I am designing a application with a bluetooth connection for displaying some data that is received from the BT. I want to use androids LiveData for communicating between layouts and classes.
I have a dedicated (non UI) thread for managing the connection with the bluetooth adapter that is responsible for receiving and sending data. I now have a problem because I cannot edit the LiveData from that thread. I have the following code for editing LiveData:
class ConnectThread(device: BluetoothDevice): Thread()
{
...
private lateinit var model: MainViewModel
override fun run() {
model = ViewModelProviders.of(this).get(MainViewModel::class.java)
model.frontleft.postValue("hello")
...
}
}
I can edit the LiveData with the upper code in my activity with a layout (UI) without a problem. But in my Connect thread I get the following error on the .of(this) section of the model code:
error .of
This is my ViewModel:
class MainViewModel : ViewModel() {
val frontleft: MutableLiveData<String> by lazy {
MutableLiveData<String>()
}
}
I have made quite a research about my problem but cannot find my answer. Is it because I want to edit the LiveData from non UI thread/fregment/activity? or because the Thread is running on a different part of the code?
this points to current context so if your code are in fragment or Activity you can access it's context like this this#YourFragmentName, this#YuorActivityName you can also check ViewModelProviders documentation
ViewModelProviders.of(this).get(MainViewModel::class.java) change this here
ViewModelProviders.of(this).get(model::class.java) internally uses a retainFragment. You need to send the value to the constructor someway or set a public observable property to set a property from the thread and use that property in the viewmodel.
var myObservable by Delegates.Observables(""){_,_,_ -> "hello"}
something like this
Related
Initially I directly accessed a viewModel funtion which launched a viewModel scope coroutine for the query I was running. That had the same error.
Then, I changed the viewModel function to a suspend function and called it from a coroutine in the fragment. that didn't work either.
So, I made it such that the function was called inside a coroutine which then ran another coroutine in the viewModel scope. That gave the same outcome.
I thought it might be too much load to call it during fragment creation. so I tried calling the viewModel function using a button onclick listener. again crashed.
I ran the same query in the database inspector where it worked fine. so, the query isn't the issue either.
In the below screenshot I have included every necessary detail regarding the issue. Just focus on the highlighted content. start from pass List Fragment(top-left tab). From there, a call to the viewModel function in the top right tab. from there the DAO right below it. then the data class below it.
Android studio screenshot -
the viewModel function -
fun resetAllAccess(){
viewModelScope.launch {
passwordDao.resetAccessForAll()
}
}
The DAO funtion -
#Query("UPDATE password_data SET access = 0 WHERE access = 1")
fun resetAccessForAll()
Data class for the database -
#Entity(tableName = "password_data")
data class PasswordData(
#PrimaryKey(autoGenerate = true) val id: Int = 0,
#ColumnInfo(name = "service_name") var serviceName: String,
#ColumnInfo(name = "service_password") var servicePassword: String,
#ColumnInfo(name = "is_an_application") var isAnApplication: Boolean = false,
#ColumnInfo(name = "use_finger_print") var useFingerPrint: Boolean = true,
#ColumnInfo(name = "access") var access: Boolean = false
)
the call being made from the fragment -
CoroutineScope(Dispatchers.IO).launch { viewModel.resetAllAccess() }
I replaced this funtion -
fun resetAllAccess(){
viewModelScope.launch {
passwordDao.resetAccessForAll()
}
}
-with
fun resetAllAccess(){
CoroutineScope(Dispatchers.IO).launch {
passwordDao.resetAccessForAll()
}
}
so now, it is no longer running on the main thread.
It's because you are using the viewModelScope. Lots of people don't know this, but viewModelScope is actually hardcoded to use the main thread instead of another one.
You can find this info in Google official documentation:
Note: The viewModelScope property of ViewModel classes is hardcoded to Dispatchers.Main. Replace it in tests by using Dispatchers.setMain with a TestCoroutineDispatcher as explained in the Easy coroutines in Android: viewModelScope blog post.
So you probably want to either pass the the coroutine scope into the view model class constructor (preferred way) or create one directly in the view model. Then you should just use this to launch the coroutine.
Another practice that is commonly used is to create a scope with both view model and a custom scope (passed through the view model constructor).
For example:
class ViewModelClass(customCoroutineScope: CoroutineScope): ViewModel {
private val backgroundScope: CoroutineContext = customCoroutineScope.coroutineContext + viewModelScope.coroutineContext
fun resetAllAccess(){
backgroundScope.launch {
passwordDao.resetAccessForAll()
}
}
}
You can also find more info here about how viewModelScope works under the hood.
There's also another option, but I wouldn't recommend this at all, for obvious reasons, that is to allow Room to run queries in the main thread by using the allowMainThreadQueries in the Room builder.
If it is Dispatcher.Main, you should change it to IO because the UI is blocked.
fun upsert(item: ShoppingItem)=GlobalScope.launch(Dispatchers.IO) { repository.upsert(item) }
I am using Dagger-Hilt for Dependency Injection, and I am stuck with not knowing how to do field injection inside an abstract class.
// #ViewModelScoped
abstract class BaseUseCase<Params, Return>{
// lateinit var not initiazlied. Cannot be injected
#Inject
lateinit var errorHandler: ErrorHandler
fun execute(#nullable params: Params?=null): Flow<DataState<Return>> = flow {
emit(Datastate.Loading)
emit(executeRealization(params))
...
}.catch{ e->
when(e){
...
is Exception -> {
...
errorHandler.handleError(e.message ?: "Unknown Error")
}
}
}
protected abstract fun executeRealization(#Nullable params: Params?=null): DataState<Return>
}
[DI package]
I provided "ErrorHandler" as a singleton using dagger-hilt (AppModule.kt)
Usecases which extend above BaseUseCase are all written for dagger-hilt (UseCaseModule.kt)
I tried providing or binding BaseUseCase class using dagger-hilt such as BaseUseCaseModule.kt, however since it has type parameters, it cannot be binded and also provided.
Currently i cannot inject errorHandler inside BaseUseCase class, so just written ErrorHandler 'object' and using it statically. (e.g. Object ErrorHandler {})
Question
How to do field injection inside abstract class?
Or Am i missing something?
How to do field injection inside an abstract class?
This is currently not supported.
You can consider refactoring your code in these two approaches.
First Approach
Move the exception/error handling up the chain towards the UI, this would include the approach of ViewModel.
With this, you can constructor inject your error handler, then execute your UseCase and wrap the handler around it.
Let's look at a possible solution, in the sketch, we'll utilize clean architecture approach;
ViewModel.kt
#HiltViewModel
class YourViewModel #Inject constructor(private val errorHandler: ErrorHandler, private val useCase : SpecificUseCase) : ViewModel(){
suspend fun realizationFunction(params : Params?=null) : Flow<DataState<Return>> = flow {
emit(Datastate.Loading)
try{
emit(useCase(params))
}catch(exception : Exception){
errorHandler.handleError(e.message ?: "Unknown Error")
}
}
}
On your specific useCase, I do recommend you use repository pattern to execute your functions in order to separate concerns instead of executing your functions inside the use case.
Second Approach
This approach involves taking the error handler deeper into the chain and constructor injecting your error handler in your repository implementation.
This would give you the chance to run the particular function/service calls inside a try/catch and handle your error in there.
The con of this second approach may include the challenge of returning the error result, but incorporating a resource class will make it seamless - seems like you have one already, DataState.
class YourRepositoryImpl(private val errorHandler: ErrorHandler) : YourRepositoryInterface {
override suspend fun doSomething(params : Params?) : Flow<DataState<Return>> {
//call your function and use the error handler
}
}
This gives you cleaner code and better error handling.
Or Am I missing something?
You may be interested in reading much about app architecture and separation of concerns.
After more than 2 years, I am "updating" myself with android/kotlin changes, and boy, has it changed a lot.
Scenario
I have a main activity with MyFragment and a MyFragmentViewModel
I have a foreground service MyService
I have a repository that has a Flow<MyState> which should be collected by both MyFragmentViewModel and MySerice
Basically in the past, when I wanted to communicate between a not exported service and the main activity I've used LocalBroadCastReceiver which worked really well and removed the tight coupling between the two. Now that is deprecated so I thought why not have in the Repository a Flow that gets collected whenever it changes, so any client can react to changes.
Here is, for the sake of simplicity, some basic code related
enum class MyState{
STATE_LOADING,
STATE_NORMAL,
....
}
class MyRepository(){
//for simplicity there is no private immutable _state for now
val state:MutableStateFlow<MyState> = MutableStateFlow(MyState.STATE_NORMAL)
fun updateState(newState: MyState){
state.value = newState
}
}
class MyFragmentViewModel #Inject constructor(
private val myRepository: MyRepository
): ViewModel(){
fun updateCurrentState(){
myRepository.updateState(MyState.STATE_LOADING)
}
}
#AndroidEntryPoint
class MyService:Service(){
#Inject lateinitvar myRepository: MyRepository
private val myJob = SupervisorJob()
private val myServiceScope = CoroutineScope(Dispachers.IO+myJob)
fun listenForState(){
myServiceScope.launch{
myRepository.state.collect{
when(it)
....
}
}
}
}
What happens is that on starting, the collect in MyService does get the initial value STATE_NORMAL but when from MyFragmentViewModel I update the MyRepository state, this value is not received by the service.
My questions:
what am I doing wrong? Is something related to service scope/coroutines and how collect works?
is this a good approach, architecturally speaking or are there better way to do it?
Your Services should never communicate with the Repository , since it should come under the UI Module and thus it must communicate to the ViewModel which further communicates to the Repository .
You can read my answer on MVVM pattern here :
Is this proper Android MVVM design?
. I have explaind the MVVM pattern here .
Also for your specific useCase , I recommend you to check this github - project :
https://github.com/mitchtabian/Bound-Services-with-MVVM
In the ReadMe section there is a link to a Youtube video which will explain you in depth about how to use Services with MVVM .
Also in your code , you have made use of enum classes which is not wrong , but since you are using you can make use of Sealed Classes , which is built on top of Enums and provides to maintain strict hierarchy .Your enum class in the form of Sealed Class will look in the following manner :
sealed class MyState{
object State_Loading : MyState()
object State_Normal : MyState()
}
And for you issue about not able to update the data , I suggest you to try
fun updateState(newState: MyState){
state.emit( newState)
}
If this does not work , you need to debug at every step from where the data passes using Log and know where is the error taking place
so in MVVM architecture even in google samples we can see things like this:
class CharacterListActivity :BaseActivity() {
val ViewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.getData() // Bad!!!
...
viewModel.state.observe(this) { state ->
when(state) { // handling state is not views job
Success -> { navigatetoNextPage() } // navigating is not views job
Progress -> { showProgress() }
NetworkError -> { ShowSnackbar(viewModel.error) } // I,m not sure about this one either
Error -> { showErrorDialog(viewModel.error)
}
}
We know that any architecture has its own rules that makes the code testable, maintainable, and scalable over time.
in MVVM pattern according to both Wikipedia and Microsoft docs this is the View:
the view is the structure, layout, and appearance of what a user sees on the screen.[6] It displays a representation of the model and receives the user's interaction with the view (clicks, keyboard, gestures, etc.), and it forwards the handling of these to the view model via the data binding (properties, event callbacks, etc.) that is defined to link the view and view model.
each view is defined in XAML, with a limited code-behind that does not contain business logic. However, in some cases, the code-behind might contain UI logic that implements visual behavior such as animations.
XAML is a Xamarin thing, so now let's get back to our code:
here, since activity decides what to do with the state, the activity works as Controller like in MVC but, activity supposed to be the View ,view is just supposed to do the UI logic.
the activity even tells the ViewModel to get data. this is again not the View's job.
please note that telling what to do to the other modules in the code is not the View's job. this is making the view act as controller. view is supposed to handle its state via callbacks from the ViewModel.
the View is supposed to just tell the ViewModel about events like onClick().
since ViewModel doesn't have access to View, it can't show a dialog or navigate through the app directly!
so what is an alternative approach to do this without violation of architecture rules? should I have a function for any lif cycle event in ViewModel, like viewModel.onCreate? or viewModel.onStart? what about navigation or showing dialogs?
For The Record I'm not mixing Up mvc and mvvm, I'm saying that this pattern does which is recommended buy google.
This is not opinion-based, surely anyone can have their own implementation of any architecture but the rules must always be followed to achieve overtime maintainability.
I can name the violations in this code one by one for you:
1) UI is not responsible for getting data, UI just needs to tell ViewModel about events.
2) UI is not responsible for handling state which is exactly what it does here. more general, UI shouldn't contain any non-UI logic.
3) UI is not responsible for navigating between screens
the activity even tells the ViewModel to get data. this is again not the View's job.
Correct. The data fetch should be triggered either by ViewModel.init, or more accurately the activation of a reactive data source (modeled by LiveData, wrapping said reactive source with onActive/onInactive).
If the fetch MUST happen as a result of create, which is unlikely, then it could be done using the DefaultLifecycleObserver using the Jetpack Lifecycle API to create a custom lifecycle-aware component.
Refer to https://stackoverflow.com/a/59109512/2413303
since ViewModel doesn't have access to View, it can't show a dialog or navigate through the app directly!
You can use a custom lifecycle aware component such as EventEmitter (or here) to send one-off events from the ViewModel to the View.
You can also refer to a slightly more advanced technique where rather than just an event, an actual command is sent down in the form of a lambda expression sent as an event, which will be handled by the Activity when it becomes available.
Refer to https://medium.com/#Zhuinden/simplifying-jetpack-navigation-between-top-level-destinations-using-dagger-hilt-3d918721d91e
typealias NavigationCommand = NavController.() -> Unit
#ActivityRetainedScoped
class NavigationDispatcher #Inject constructor() {
private val navigationEmitter: EventEmitter<NavigationCommand> = EventEmitter()
val navigationCommands: EventSource<NavigationCommand> = navigationEmitter
fun emit(navigationCommand: NavigationCommand) {
navigationEmitter.emit(navigationCommand)
}
}
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
#Inject
lateinit var navigationDispatcher: NavigationDispatcher
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
navigationDispatcher.navigationCommands.observe(this) { command ->
command.invoke(Navigation.findNavController(this, R.id.nav_host))
}
}
}
class LoginViewModel #ViewModelInject constructor(
private val navigationDispatcher: NavigationDispatcher,
#Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel() {
fun onRegisterClicked() {
navigationDispatcher.emit {
navigate(R.id.logged_out_to_registration)
}
}
}
If Hilt is not used, the equivalent can be done using Activity-scoped ViewModel and custom AbstractSavedStateViewModelFactory subclasses.
In my ViewModel I am calling a method from my repository class which returns a LiveData from a webservice. My viewModel code:
class MainViewModel #Inject constructor(val mainRepository: MainRepository) : ViewModel() {
val source: LiveData<My_Result> = mainRepository.fetchApiresultFromClient(str_query)
.......... }
My question is that is there a way to get real data from the webservice called in repository or do I just prepare the result and assert that its not null; something like below:
when(mainrepository.fetchApiresultFromClient(any(String::class))).thenReturn(myPreparedLiveData<My_result>)
As the question is tagged as unit-testing, IMHO, expecting real data from a web service does not actually fall into the scope of unit-testing. You might call than an integration testing, however, from the unit-testing point of view, you might consider mocking the response from the function that calls the web service and verify if the method was called using proper arguments that you expect.