Android-CleanArchitecture-Kotlin icon indicating copy to clipboard operation
Android-CleanArchitecture-Kotlin copied to clipboard

Returning LiveData from domain layer

Open pablobaldez opened this issue 6 years ago • 21 comments

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)?

pablobaldez avatar May 10 '18 16:05 pablobaldez

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

St4B avatar May 11 '18 08:05 St4B

AFAIK livedata can work even in unit tests?

Zhuinden avatar May 11 '18 09:05 Zhuinden

@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.

pablobaldez avatar May 11 '18 23:05 pablobaldez

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

St4B avatar May 14 '18 07:05 St4B

@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

pablobaldez avatar May 15 '18 01:05 pablobaldez

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.

android10 avatar May 17 '18 08:05 android10

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.

nikolajakshic avatar May 21 '18 15:05 nikolajakshic

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.

crjacinro avatar Sep 12 '18 18:09 crjacinro

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.

nikolajakshic avatar Sep 12 '18 18:09 nikolajakshic

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.

SaeedMasoumi avatar Sep 14 '18 08:09 SaeedMasoumi

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.

mitevyav avatar Oct 18 '18 07:10 mitevyav

@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.

nikolajakshic avatar Oct 18 '18 10:10 nikolajakshic

@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 avatar Oct 28 '18 21:10 lawloretienne

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.

lawloretienne avatar Oct 28 '18 21:10 lawloretienne

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

IMDroidude avatar Oct 21 '22 09:10 IMDroidude

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. 😂

Zhuinden avatar Oct 22 '22 13:10 Zhuinden

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

That's it!

vasilyev04 avatar Nov 06 '23 06:11 vasilyev04

RxJava / Reaktive are technically both valid options

Zhuinden avatar Nov 06 '23 09:11 Zhuinden

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 :)

vasilyev04 avatar Nov 06 '23 16:11 vasilyev04

In that case, just use LiveData 😉

Zhuinden avatar Nov 06 '23 17:11 Zhuinden

In that case, just use LiveData 😉

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

vasilyev04 avatar Nov 06 '23 19:11 vasilyev04