android10 / Android-CleanArchitecture-Kotlin

This is a movies sample app in Kotlin, which is part of a serie of blog posts I have written about architecting android application using different approaches.
https://fernandocejas.com/2018/05/07/architecting-android-reloaded/
4.63k stars 921 forks source link

Returning LiveData from domain layer #14

Open pablobaldez opened 6 years ago

pablobaldez commented 6 years ago

What do you think about returning LiveData instances directly from domain Layer? I'm asking it because the one of the best features of Room framework is the hability to observe changes in DB directly.

So the LiveData instances should be returned directly from domain (usecase and repositories)?

St4B commented 6 years ago

LiveData is part of the framework so i would not use it in the domain layer.

Zhuinden commented 6 years ago

AFAIK livedata can work even in unit tests?

pablobaldez commented 6 years ago

@St4B I don't think is a good aproach lose Room features of update LiveData just because its part of framework. This rule is very important to make the unit tests easier, but we can test livedata using Junit without any problem, just like another project class.

St4B commented 6 years ago

hmmm ... I was affected by the old project (with rxJava) which had different models per layers and mappers. Now that we use the same model in every layer it does not seem wrong to me. Basically I started to like it! : p

pablobaldez commented 6 years ago

@Zhuinden yes. You have to add dependencies of livedata to unit test android.arch.core:core-testing:$arch_version and than add the rule InstantTaskExecutorRule into unit test file

android10 commented 6 years ago

Totally agree here with the above comments.

LiveData belongs to the Android Framework which is something you wanna avoid as much as you can, at least at domain level.

In my opinion, it clearly belong to the UI layer, being a key part of MVVM.

nikolajakshic commented 6 years ago

If you use RxJava in your project, the solution might be to return the Observable from the domain layer and use the reactive streams to convert it to the LiveData in the UI layer.

crjacinro commented 5 years ago

If you use RxJava in your project, the solution might be to return the Observable from the domain layer and use the reactive streams to convert it to the LiveData in the UI layer.

Is this available already in the source code? This would be a big help since I want to take advantage of Room -> LIve Data interaction by using this clean arch repository.

nikolajakshic commented 5 years ago

If you use RxJava in your project, the solution might be to return the Observable from the domain layer and use the reactive streams to convert it to the LiveData in the UI layer.

Is this available already in the source code? This would be a big help since I want to take advantage of Room -> LIve Data interaction by using this clean arch repository.

No, this sample app is not using RxJava.

SaeedMasoumi commented 5 years ago

I agree that the domain layer must not have a dependency on the platform-specific frameworks/libraries, but based on YAGNI principle we're just adding an extra level of abstraction without gain.
So I think passing LiveData in domain layer give us some advantages like less boilerplate for some features like Room.

mitevyav commented 5 years ago

I am kind a new to the Clean Approach, but there was always something that made my crazy in the begging when I tried to understand it. Everywhere, like in here, everybody are saying - just plain Java! No Android, not this, no that, but every time RxJava is the solution of everything. But RxJava is not pure Java!! I swear, I have never seen an article saying - "Here we are using RxJava, and we are breaking the principle, but there are more pros than cons, so it is OK!". So saying this I don't see why RxJava is ok, but LiveData not.

nikolajakshic commented 5 years ago

@mitevyav Well, RxJava is not something that is Android specific, it can be used everywhere (web, mobile, desktop, etc..), on the other side, LiveData is Android specfic, that's why it's "acceptable" to return something like Observable from the domain layer, and for LiveData it is not. It's just that RxJava is so popular, widely accepted and some will say it feels almost like a part of the language.

IMO, you don't need to strictly follow clean "rules", just pick what suits your use-case the best and as long as your app is testable and maintainable you are good.

lawloretienne commented 5 years ago

@Zhuinden yes. You have to add dependencies of livedata to unit test android.arch.core:core-testing:$arch_version and than add the rule InstantTaskExecutorRule into unit test file

After adding the core-testing dependency

testImplementation "androidx.arch.core:core-testing:2.0.0"

it throws an error that has do with with Powermockito.

java.lang.AbstractMethodError: org.powermock.api.mockito.internal.mockmaker.PowerMockMaker.isTypeMockable(Ljava/lang/Class;)Lorg/mockito/plugins/MockMaker$TypeMockability;

at org.mockito.internal.util.MockUtil.typeMockabilityOf(MockUtil.java:29)
at org.mockito.internal.util.MockCreationValidator.validateType(MockCreationValidator.java:22)
at org.mockito.internal.creation.MockSettingsImpl.validatedSettings(MockSettingsImpl.java:232)
at org.mockito.internal.creation.MockSettingsImpl.build(MockSettingsImpl.java:226)
at org.mockito.internal.MockitoCore.mock(MockitoCore.java:64)
at org.mockito.Mockito.mock(Mockito.java:1871)
at org.mockito.internal.configuration.MockAnnotationProcessor.process(MockAnnotationProcessor.java:36)
at org.mockito.internal.configuration.MockAnnotationProcessor.process(MockAnnotationProcessor.java:16)
at org.mockito.internal.configuration.IndependentAnnotationEngine.createMockFor(IndependentAnnotationEngine.java:38)
at org.mockito.internal.configuration.IndependentAnnotationEngine.process(IndependentAnnotationEngine.java:62)
at org.mockito.internal.configuration.InjectingAnnotationEngine.processIndependentAnnotations(InjectingAnnotationEngine.java:57)
at org.mockito.internal.configuration.InjectingAnnotationEngine.process(InjectingAnnotationEngine.java:41)
at org.mockito.MockitoAnnotations.initMocks(MockitoAnnotations.java:69)
at com.etiennelawlor.pitted.TopSpotsViewModelTest.setUp(TopSpotsViewModelTest.kt:53)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
at org.junit.rules.TestWatcher$1.evaluate(TestWatcher.java:55)
at org.junit.rules.RunRules.evaluate(RunRules.java:20)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

This error gets thrown after you call the following function in the setUp function

MockitoAnnotations.initMocks(this)
lawloretienne commented 5 years ago

So i commented out these dependencies

// testImplementation “org.mockito:mockito-all:$mockitoVersion” // testImplementation “org.hamcrest:hamcrest-all:$hamcrestVersion” // testImplementation “org.powermock:powermock-module-junit4:2.0.0-RC.1” // testImplementation “org.powermock:powermock-api-mockito:1.7.4"

and now i just have these depenedencies

testImplementation “junit:junit:$junitVersion” testImplementation “org.mockito:mockito-core:2.21.0"

and there is no longer a conflict.

IMDroidude commented 1 year ago

Why don't you go for Flow instead of LiveData. as Flow is a pure data driven item.

Zhuinden commented 1 year ago

LiveData is part of the framework so i would not use it in the domain layer.

Objectively, if you don't want to use LiveData, then you shouldn't use Room.

Room is also Android-specific.

Honestly, we should just all use Flutter, you can port that to any platform now. 😂

vasilyev04 commented 8 months ago

Definitely, at the moment there is only one solution for this problem and it's using Coroutine Flow instead of LiveData.

That's it!

Zhuinden commented 8 months ago

RxJava / Reaktive are technically both valid options

vasilyev04 commented 8 months ago

RxJava / Reaktive are technically both valid options

I suppose that it's not necessary to bring huge framework in your project just for gaining data from Room :)

Zhuinden commented 8 months ago

In that case, just use LiveData 😉

vasilyev04 commented 8 months ago

In that case, just use LiveData 😉

The question is "how to avoid liveData in domain layer?", so..