Method addObserver must be called on the main thread Exception, While inserting data to room database - android-room

I am trying to insert data into the room database using the kotlin coroutine. But I always get an exception java.lang.IllegalStateException: Method addObserver must be called on the main thread
But I don't have an observer in this code, the insert call is called from launch with Dispatchers IO
DocumentDao.kt
#Dao
interface DocumentDao {
#Insert
suspend fun insertDocument(document: Document): Long
}
Repository.kt
class Repository#Inject constructor(val db: MyDB) {
suspend fun demoInsert(
uri: String,
albumId: Long
): Long {
val newDoc = Document(0, albumId, rawUri = uri)
return db.documentDao().insertDocument(newDoc)
}
}
MyViewModel.kt
#HiltViewModel
class MyViewModel#Inject constructor(val repo: Repository) : ViewModel() {
suspend fun demoInsert(
uri: String,
albumId: Long
): Long {
return repo.demoInsert(uri, albumId)
}
}
MyFrag.kt
#AndroidEntryPoint
class MyFrag: Fragment() {
val viewModel: MyViewModel by viewModels()
....
....
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.insert.setOnClickListener {
lifecycleScope.launch(Dispatchers.IO) {
val res = viewModel.demoInsert("test", Random.nextLong(500))
Log.d(TAG, "onViewCreated: $res")
}
}
........
.......
}
}
what is wrong with this code? please help me

I'm not sure about this but you can launch coroutine inside listener with Main Dispatcher and later use withContext inside DB function, to change context.

I was facing the same issue and I solved yhis way :
private fun insertAllItemsInDb(data : List<PostResponse>){
val listPost = data.map { it.toUI() }
val scope = CoroutineScope(Job() + Dispatchers.Main)
scope.launch {
localViewModel.insertAllPosts(listPost)
}
}
ViewModel:
fun insertAllPosts(posts: List<PostItem>) {
viewModelScope.launch {
dbRepository.insertAllPosts(posts)
}
}

Creating view model with:
val viewModel: MyViewModel by viewModels()
Will result in lazy creating. Creation of real object will be performed when you access your object for first time. This happens inside:
lifecycleScope.launch(Dispatchers.IO) {
val res = viewModel.demoInsert("test", Random.nextLong(500))
Log.d(TAG, "onViewCreated: $res")
}
And since implementation of method viewModels<>() looks like this:
#MainThread
public inline fun <reified VM : ViewModel> Fragment.viewModels(
noinline ownerProducer: () -> ViewModelStoreOwner = { this },
noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> = createViewModelLazy(VM::class, { ownerProducer().viewModelStore }, factoryProducer)
You are getting
Method addObserver must be called on the main thread
You should be able to fix this with something like this.
lifecycleScope.launch(Dispatchers.IO) {
val res = withContext(Dispatchers.Main + lifecycleScope.coroutineContext){}.demoInsert("test", Random.nextLong(500))
Log.d(TAG, "onViewCreated: $res")
}

MyViewModel.kt
#HiltViewModel
class MyViewModel#Inject constructor(val repo: Repository) : ViewModel() {
suspend fun demoInsert(
uri: String,
albumId: Long
): Long {
viewModelScope.launch {
repo.demoInsert(uri, albumId)
}
}
}
MyFrag.kt
#AndroidEntryPoint
class MyFrag: Fragment() {
val viewModel: MyViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.insert.setOnClickListener {
lifecycleScope.launch(Dispatchers.Main) {
viewModel.demoInsert("test", Random.nextLong(500))
}
}
}
}

Related

Testable Kotlin objects that uses a dispatcher in init block?

I have a class that looks like this:
object SomeRepository {
private val logger = Logger(this)
private val flow: MutableStateFlow<List<SomeClass>> = MutableStateFlow(listOf())
init {
GlobalScope.launch(Dispatchers.IO) {
// some code
}
}
fun list() = flow.value
fun observe(): StateFlow<List<SomeClass>> = flow
}
It is recommended to inject dispatchers in tests.
Also, there is a risk to get weird problems such as AppNotIdleException if not following this practice.
One option would be to make these into variables and add setters, but not very nice. Also it creates a race condition (init-block vs setters):
object SomeRepository {
private val logger = Logger(this)
private val flow: MutableStateFlow<List<SomeClass>> = MutableStateFlow(listOf())
private var coroutineScope: CoroutineScope = GlobalScope
private var dispatcher: CoroutineDispatcher = Dispatchers.IO
init {
dispatcher.launch(coroutineScope) {
// some code
}
}
fun list() = flow.value
fun observe(): StateFlow<List<SomeClass>> = flow
#VisibleForTesting
fun setDispatcher(dispatcher: CoroutineDispatcher) {
this.dispatcher = dispatcher
}
#VisibleForTesting
fun setCoroutineScope(coroutineScope: CoroutineScope) {
this.coroutineScope = coroutineScope
}
}
Version that avoids the race condition (test needs to explicitly invoke init()):
object SomeRepository {
private val logger = Logger(this)
private val flow: MutableStateFlow<List<SomeClass>> = MutableStateFlow(listOf())
private var coroutineScope: CoroutineScope = GlobalScope
private var dispatcher: CoroutineDispatcher = Dispatchers.IO
init {
// var isInTesting = Build.FINGERPRINT == "robolectric"
if (!isInTesting) {
init()
}
}
#VisibleForTesting
fun init() {
dispatcher.launch(coroutineScope) {
// some code
}
}
fun list() = flow.value
fun observe(): StateFlow<List<SomeClass>> = flow
#VisibleForTesting
fun setDispatcher(dispatcher: CoroutineDispatcher) {
this.dispatcher = dispatcher
}
#VisibleForTesting
fun setCoroutineScope(coroutineScope: CoroutineScope) {
this.coroutineScope = coroutineScope
}
}
How can I inject a coroutine scope and dispatcher to my tests and avoid variables?

I have a problem with the code in ViewModel + Kotlin Coroutines + LiveData

I have a ViewModel
#HiltViewModel
class LoginViewModel #Inject constructor(
private val apiRepository: ApiRepository
) : ViewModel() {
private val account = MutableLiveData<String>("123")
private val password = MutableLiveData<String>("123")
val message: MutableLiveData<String> = MutableLiveData()
var loginResult: LiveData<Resource<UserInfo>> = MutableLiveData()
fun signIn() {
if (TextUtils.isEmpty(account.value)) {
message.postValue("Please enter your account")
return
}
if (TextUtils.isEmpty(password.value)) {
message.postValue("please enter your password")
return
}
// In this code, it doesn’t work. I think it’s because I didn’t observe it.
// Is there any better way to write it here?
loginResult = apiRepository.signIn(account.value!!, password.value!!)
}
fun inputAccount(accountValue: String) {
account.value = accountValue
}
fun inputPassword(passwordValue: String) {
password.value = passwordValue
}
}
This is my interface
#AndroidEntryPoint
class LoginActivity : BaseActivity<ActivityLoginBinding>() {
private val viewModel: LoginViewModel by viewModels()
......
override fun initEvent() {
binding.account.editText!!.addTextChangedListener { viewModel.inputAccount(it.toString()) }
binding.password.editText!!.addTextChangedListener { viewModel.inputPassword(it.toString()) }
binding.signIn.setOnClickListener {
viewModel.signIn()
}
}
override fun setupObservers() {
viewModel.message.observe(this) {
Snackbar.make(binding.root, it, Snackbar.LENGTH_SHORT).show()
}
/**
* There will be no callback here, I know it’s because I’m observing
* `var loginResult: LiveData<Resource<UserInfo>> = MutableLiveData()`
* instead of `apiRepository.signIn(account.value!!, password.value!!)`
* because it was reassigned
*/
viewModel.loginResult.observe(this) {
Log.d("TAG", "setupObservers: $it")
}
}
}
So I adjusted the code a bit
LoginViewModel.signIn
fun signIn(): LiveData<Resource<UserInfo>>? {
if (TextUtils.isEmpty(account.value)) {
message.postValue("Please enter your account")
return null
}
if (TextUtils.isEmpty(password.value)) {
message.postValue("please enter your password")
return null
}
return apiRepository.signIn(account.value!!, password.value!!)
}
LoginActivity.initEvent
override fun initEvent() {
binding.signIn.setOnClickListener {
viewModel.signIn()?.observe(this) {
Log.d("TAG", "setupObservers: $it")
}
}
}
I have checked the official documents of LiveData, and all call livedata{} during initialization. There has been no re-assignment, but if you log in, you cannot directly start the application and request the network.
coroutines doucument
Although I finally achieved my results, I think this is not the best practice, so I want to ask for help!
Supplementary code
ApiRepository
class ApiRepository #Inject constructor(
private val apiService: ApiService
) : BaseRemoteDataSource() {
fun signIn(account: String, password: String) =
getResult { apiService.signIn(account, password) }
}
BaseRemoteDataSource
abstract class BaseRemoteDataSource {
protected fun <T> getResult(call: suspend () -> Response<T>): LiveData<Resource<T>> =
liveData(Dispatchers.IO) {
try {
val response = call.invoke()
if (response.isSuccessful) {
val body = response.body()
if (body != null) emit(Resource.success(body))
} else {
emit(Resource.error<T>(" ${response.code()} ${response.message()}"))
}
} catch (e: Exception) {
emit(Resource.error<T>(e.message ?: e.toString()))
}
}
}
Or i write like this
fun signIn() {
if (TextUtils.isEmpty(account.value)) {
message.postValue("Please enter your account")
return
}
if (TextUtils.isEmpty(password.value)) {
message.postValue("please enter your password")
return
}
viewModelScope.launch {
repository.signIn(account.value, password.value).onEach {
loginResult.value = it
}
}
}
But I think this is not perfect

DomainService not registered in AppService

I am trying to incorporate DomainService into my application, and tried to do it like the code below shows.
Here is the sample code for the manager:
namespace FlexSped.DefaultColors
{
public class DefaultColorManager : FlexSpedDomainServiceBase, IDefaultColorsManager
{
private readonly IRepository<DefaultColor> _defaultColorRepository;
public DefaultColorManager(IRepository<DefaultColor> defColorRep)
{
_defaultColorRepository = defColorRep;
}
public async Task Create(DefaultColor input)
{
await _defaultColorRepository.InsertAsync(input);
}
public Task Update(int id)
{
throw new NotImplementedException();
}
}
}
And this is the application service:
namespace FlexSped.DefaultColors
{
[AbpAuthorize(AppPermissions.Pages_Administration_DefaultColors)]
public class DefaultColorsAppService : FlexSpedAppServiceBase, IDefaultColorsAppService
{
private readonly IDefaultColorsManager _defaultColorManager;
private readonly IRepository<DefaultColor> _defaultColorRepository;
//private readonly IIocResolver _iocResolver;
public DefaultColorsAppService(IRepository<DefaultColor> defaultColorRepository, IDefaultColorsManager defColManager)
{
_defaultColorRepository = defaultColorRepository;
_defaultColorManager = defColManager;
//_iocResolver = iocResolver;
}
public async Task CreateOrEdit(CreateOrEditDefaultColorDto input)
{
if (input.Id == null)
{
await Create(input);
}
else
{
await Update(input);
}
}
[AbpAuthorize(AppPermissions.Pages_Administration_DefaultColors_Create)]
private async Task Create(CreateOrEditDefaultColorDto input)
{
DefaultColor dt = ObjectMapper.Map<DefaultColor>(input);
await _defaultColorManager.Create(dt);
}
}
}
All this produces this error:
'FlexSped.DefaultColors.DefaultColorsAppService' is waiting for the following dependencies:
- Service 'FlexSped.DefaultColors.IDefaultColorsManager' which was not registered.
Not sure what the problem is.I was following convention here.
ABPboiler is registering the services dependencies by name so your should match the implementation with definition. In your case:
IDefaultColors.IDefaultColorsManager should be IDefaultColors.IDefaultColorManager
or vice versa DefaultColorManager should be DefaultColorsManager.

Spring reactor parallel flux is stuck

I am using reactor to create an infinite flux,
once I make it parallel, the stream gets stuck after the first passed value, can't figure out why
val source = source().parallel().runOn(Schedulers.parallel())
.map(this::toUpperCase)
.subscribe(sink())
private fun sink() = SimpleSink<SimpleDaoModel>()
private fun toUpperCase(simpleDaoModel: SimpleDaoModel) = simpleDaoModel.copy(stringValue = simpleDaoModel.stringValue.toUpperCase())
private fun source() = Flux.create { sink: FluxSink<SimpleDaoModel> ->
fun getNextAsync(): Job = GlobalScope.launch(Dispatchers.Default) {
val task = customSimpleModelRepository.getNextTask()
if (task != null) {
logger.info("emitting next task")
sink.next(task)
} else {
logger.info("No more tasks")
Timer("nextTaskBackoff", false).schedule(1000) {
getNextAsync()
}
}
}
sink.onRequest { getNextAsync() }
}
class SimpleSink<T> : BaseSubscriber<T>() {
public override fun hookOnSubscribe(subscription: Subscription) {
println("Subscribed")
request(1)
}
public override fun hookOnNext(value: T) {
println(value)
request(1)
}
}
If I remove the parallel operator, everything works like a charm.
Note: getNextTask is a suspended function

Merging multiple custom observables in RX

Trying to model a system sending out notifications from a number of publishers using RX.
I have two custom interfaces ITopicObservable and ITopicObserver to model the fact that the implementing classes will have other properties and methods apart from the IObservable and IObserver interfaces.
The problem I have is that my thinking is I should be able to add a number of observables together, merge them together and subscribe to an observer to provide updates from all merged observables. However the code with "the issue" comment throws an invalid cast exception.
The use case is a number of independent sensors each monitoring a temperature in a box for example that aggregate all their reports to one temperature report which is then subscribed to by a temperature health monitor.
What am I missing here? Or is there a better way to implement the scenario using RX?
Code below
using System;
using System.Reactive.Linq;
using System.Collections.Generic;
namespace test
{
class MainClass
{
public static void Main (string[] args)
{
Console.WriteLine ("Hello World!");
var to = new TopicObserver ();
var s = new TopicObservable ("test");
var agg = new AggregatedTopicObservable ();
agg.Add (s);
agg.Subscribe (to);
}
}
public interface ITopicObservable<TType>:IObservable<TType>
{
string Name{get;}
}
public class TopicObservable:ITopicObservable<int>
{
public TopicObservable(string name)
{
Name = name;
}
#region IObservable implementation
public IDisposable Subscribe (IObserver<int> observer)
{
return null;
}
#endregion
#region ITopicObservable implementation
public string Name { get;private set;}
#endregion
}
public class AggregatedTopicObservable:ITopicObservable<int>
{
List<TopicObservable> _topics;
private ITopicObservable<int> _observable;
private IDisposable _disposable;
public AggregatedTopicObservable()
{
_topics = new List<TopicObservable>();
}
public void Add(ITopicObservable<int> observable)
{
_topics.Add ((TopicObservable)observable);
}
#region IObservable implementation
public IDisposable Subscribe (IObserver<int> observer)
{
_observable = (ITopicObservable<int>)_topics.Merge ();
_disposable = _observable.Subscribe(observer);
return _disposable;
}
#endregion
#region ITopicObservable implementation
public string Name { get;private set;}
#endregion
}
public interface ITopicObserver<TType>:IObserver<TType>
{
string Name{get;}
}
public class TopicObserver:ITopicObserver<int>
{
#region IObserver implementation
public void OnNext (int value)
{
Console.WriteLine ("next {0}", value);
}
public void OnError (Exception error)
{
Console.WriteLine ("error {0}", error.Message);
}
public void OnCompleted ()
{
Console.WriteLine ("finished");
}
#endregion
#region ITopicObserver implementation
public string Name { get;private set;}
#endregion
}
}
My first thought, is that you shouldn't implement IObservable<T>, you should compose it by exposing it as a property or the result of a method.
Second thought is that there are operators in Rx that excel at merging/aggregating multiple sequences together.
You should favor using those.
Third, which is similar to the first, you generally don't implement IObserver<T>, you just subscribe to the observable sequence and provide delegates for each call back (OnNext, OnError and OnComplete)
So your code basically is reduced to
Console.WriteLine("Hello World!");
var topic1 = TopicListener("test1");
var topic2 = TopicListener("test2");
topic1.Merge(topic2)
.Subscribe(
val => { Console.WriteLine("One of the topics published this value {0}", val);},
ex => { Console.WriteLine("One of the topics errored. Now the whole sequence is dead {0}", ex);},
() => {Console.WriteLine("All topics have completed.");});
Where TopicListener(string) is just a method that returns IObservable<T>.
The implementation of the TopicListener(string) method would most probably use Observable.Create.
It may help to see examples of mapping Rx over a Topic based messaging system.
There is an example of how you can layer Rx over TibRv topics here https://github.com/LeeCampbell/RxCookbook/blob/master/IO/Comms/TibRvSample.linq
The signature of the .Merge(...) operator that you're using is:
IObservable<TSource> Merge<TSource>(this IEnumerable<IObservable<TSource>> sources)
The actual type returned by this .Merge() is:
System.Reactive.Linq.ObservableImpl.Merge`1[System.Int32]
...so it should be fairly clear that calling (ITopicObservable<int>)_topics.Merge(); would fail.
Lee's advice not to implement either of IObservable<> or IObserver<> is the correct one. It leads to errors like the one above.
If you had to do something like this, I would do it this way:
public interface ITopic
{
string Name { get; }
}
public interface ITopicObservable<TType> : ITopic, IObservable<TType>
{ }
public interface ITopicSubject<TType> : ISubject<TType>, ITopicObservable<TType>
{ }
public interface ITopicObserver<TType> : ITopic, IObserver<TType>
{ }
public class Topic
{
public string Name { get; private set; }
public Topic(string name)
{
this.Name = name;
}
}
public class TopicSubject : Topic, ITopicSubject<int>
{
private Subject<int> _subject = new Subject<int>();
public TopicSubject(string name)
: base(name)
{ }
public IDisposable Subscribe(IObserver<int> observer)
{
return _subject.Subscribe(observer);
}
public void OnNext(int value)
{
_subject.OnNext(value);
}
public void OnError(Exception error)
{
_subject.OnError(error);
}
public void OnCompleted()
{
_subject.OnCompleted();
}
}
public class AggregatedTopicObservable : Topic, ITopicObservable<int>
{
List<ITopicObservable<int>> _topics = new List<ITopicObservable<int>>();
public AggregatedTopicObservable(string name)
: base(name)
{ }
public void Add(ITopicObservable<int> observable)
{
_topics.Add(observable);
}
public IDisposable Subscribe(IObserver<int> observer)
{
return _topics.Merge().Subscribe(observer);
}
}
public class TopicObserver : Topic, ITopicObserver<int>
{
private IObserver<int> _observer;
public TopicObserver(string name)
: base(name)
{
_observer =
Observer
.Create<int>(
value => Console.WriteLine("next {0}", value),
error => Console.WriteLine("error {0}", error.Message),
() => Console.WriteLine("finished"));
}
public void OnNext(int value)
{
_observer.OnNext(value);
}
public void OnError(Exception error)
{
_observer.OnError(error);
}
public void OnCompleted()
{
_observer.OnCompleted();
}
}
And run it with:
var to = new TopicObserver("watching");
var ts1 = new TopicSubject("topic 1");
var ts2 = new TopicSubject("topic 2");
var agg = new AggregatedTopicObservable("agg");
agg.Add(ts1);
agg.Add(ts2);
agg.Subscribe(to);
ts1.OnNext(42);
ts1.OnCompleted();
ts2.OnNext(1);
ts2.OnCompleted();
Which gives:
next 42
next 1
finished
But apart from being able to give everything a name (which I'm not sure how it helps) you could always do this:
var to =
Observer
.Create<int>(
value => Console.WriteLine("next {0}", value),
error => Console.WriteLine("error {0}", error.Message),
() => Console.WriteLine("finished"));
var ts1 = new Subject<int>();
var ts2 = new Subject<int>();
var agg = new [] { ts1, ts2 }.Merge();
agg.Subscribe(to);
ts1.OnNext(42);
ts1.OnCompleted();
ts2.OnNext(1);
ts2.OnCompleted();
Same output with no interfaces and classes.
There's even a more interesting way. Try this:
var to =
Observer
.Create<int>(
value => Console.WriteLine("next {0}", value),
error => Console.WriteLine("error {0}", error.Message),
() => Console.WriteLine("finished"));
var agg = new Subject<IObservable<int>>();
agg.Merge().Subscribe(to);
var ts1 = new Subject<int>();
var ts2 = new Subject<int>();
agg.OnNext(ts1);
agg.OnNext(ts2);
ts1.OnNext(42);
ts1.OnCompleted();
ts2.OnNext(1);
ts2.OnCompleted();
var ts3 = new Subject<int>();
agg.OnNext(ts3);
ts3.OnNext(99);
ts3.OnCompleted();
This produces:
next 42
next 1
next 99
It allows you to add new source observables after the merge!

Resources