igorwojda / android-showcase Goto Github PK
View Code? Open in Web Editor NEW💎 Android application following best practices: Kotlin, Coroutines, JetPack, Clean Architecture, Feature Modules, Tests, MVVM, DI, Static Analysis...
License: MIT License
💎 Android application following best practices: Kotlin, Coroutines, JetPack, Clean Architecture, Feature Modules, Tests, MVVM, DI, Static Analysis...
License: MIT License
The first time when you run the app, it will display normally, and the second time it will crash. The error is as follows:
java.net.SocketTimeoutException: failed to connect to ws.audioscrobbler.com/64.30.224.206 (port 80) from /10.60.206.65 (port 40406) after 10000ms at libcore.io.IoBridge.connectErrno(IoBridge.java:185) at libcore.io.IoBridge.connect(IoBridge.java:130) at java.net.PlainSocketImpl.socketConnect(PlainSocketImpl.java:129) at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:356) at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:200) at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:182) at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:356) at java.net.Socket.connect(Socket.java:616) at okhttp3.internal.platform.AndroidPlatform.connectSocket(AndroidPlatform.kt:56) at okhttp3.internal.connection.RealConnection.connectSocket(RealConnection.kt:268) at okhttp3.internal.connection.RealConnection.connect(RealConnection.kt:176) at okhttp3.internal.connection.ExchangeFinder.findConnection(ExchangeFinder.kt:236) at okhttp3.internal.connection.ExchangeFinder.findHealthyConnection(ExchangeFinder.kt:109) at okhttp3.internal.connection.ExchangeFinder.find(ExchangeFinder.kt:77) at okhttp3.internal.connection.Transmitter.newExchange$okhttp(Transmitter.kt:162) at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.kt:35) at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:112) at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:87) at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.kt:82) at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:112) at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:87) at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.kt:84) at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:112) at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.kt:71) at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:112) at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:87) at okhttp3.logging.HttpLoggingInterceptor.intercept(HttpLoggingInterceptor.kt:215) at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:112) at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:87) at com.igorwojda.showcase.app.data.retrofit.UserAgentInterceptor.intercept(UserAgentInterceptor.kt:23) at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:112) at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:87) at com.igorwojda.showcase.app.data.retrofit.AuthenticationInterceptor.intercept(AuthenticationInterceptor.kt:18) at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:112) at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:87) at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.kt:184) at okhttp3.RealCall$AsyncCall.run(RealCall.kt:136) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636) at java.lang.Thread.run(Thread.java:764)
Well! This seems to be a problem with the network.
We can simplify the project code
https://twitter.com/igorwojda/status/1245344121155395587
Need a seperate branch with hilt implementation
Hi, would you help me understanding the purpose of the following line? I am quite new to NAC and I don't really understand the goal of NavHostActivity.
Thanks!
I/okhttp.OkHttpClient: --> POST http://ws.audioscrobbler.com/2.0/?method=album.search&album=sd&limit=60&api_key=70696db59158cb100370ad30a7a705c1&format=json
I/okhttp.OkHttpClient: Content-Length: 0
I/okhttp.OkHttpClient: User-Agent: showcase/1.0 Dalvik/2.1.0 (Linux; U; Android 10; Android SDK built for x86 Build/QSR1.200715.002)
I/okhttp.OkHttpClient: --> END POST (0-byte body)
I/okhttp.OkHttpClient: <-- HTTP FAILED: java.io.IOException: unexpected end of stream on http://ws.audioscrobbler.com/...
ArchUnit is a framework that allows to test app architecture. It looks like it's not suited for android, so some research, experimentation and potentially contribution to ArchUnit
may be required to make it work with android project.
TNG/ArchUnit#241
Few tests that we could write with ArchUnit
...domain
layer does not uses any classes from other layers (have imports from other layers).androidx.lifecycle.ViewModel
child class has ViewModel
suffixDo you really need the staticCheck task in your gradle conf?
Couldn't you just do "./gradlew check" to perform all the tasks related to tests and deteckt/ktlint/etc.?
On top of quality checks with ktlint and detekt, it could be interesting to add coverage to your reports. I didn't find any mention of JaCoCo or any other option in your gradle files.
Hi @igorwojda Thanks for great sample project.
Did you plan to add also support for Db layer ?
if not in the near future, can you please help design what will be the best approach to add db representation, in order to be accesses from all the modules.
Thanks,
Ronny.
1.how to navigate from featureA to start of featureB without BottomNavController?
if i try navigate from graphFeatureA to graphFeatureB as action, i got a "destination not found exception"
2. how to navigate from featureA to featureB any Fragment?
3. Can i use safeargs in both scenario?
Package it into an apk to install and open the program to crash (debugging is normal)
Initially I was against, but now I think that we could change source set folder from java
to kotlin
.
Things to consider:
main
and test
source setsMore read:
https://handstandsam.com/2020/04/12/where-should-i-put-kotlin-code-in-an-android-project/
http://facebook.github.io/stetho/ is no longer being worked on.
https://fbflipper.com/ is the new Stetho, also from Facebook. Replace
Hi @igorwojda thanks for this project, its super awesome and have used your approach a couple of times on my projects while setting up modules and more so i particularly like your Kotlin Gradle DSL Setup, couldn't find any other complete sample.
However i think you should check on accessing the buildSrc project and its dependencies in settings scripts as it has been deprecated in Gradle v 6.0.
Am currently looking on a work around for the same. In case i find a workaround before you update, will definitely create a PR for this :]
Seems that the navigation stopped working after #153.
Nothing happens when I click on an album because this check never returns true:
If I'm not mistaken, currentFragment?.id
returns the id of the fragment's container. It's not the same as currentDestination.id
, which is specified in navigation graph xml.
Hi, I have cloned your repo and wait for build. Everything was ok. But, I don't see any configuration to run. I have also try gradle sync, inv. cache and restart but nothing works.
Android studio version is: 3.5.3
If there's no internet connection, there's a crash without any descriptive log what happened, where and why. This sample needs some error handling :)
Hello,
thank you for this repo.I would like to ask, when working together with Dynamic Feature modules and Navigation components, I directly configured the Fragment path of Dynamic Feature module in the Navigation. However, when I was running the project, the system told me that I could not find the target.I noticed that your project is also written in the same way, but the runtime is normal. May I ask if it is my configuration error?Now I have changed the configuration consistent with yours, but it still cannot solve this problem. If you have time trouble, you can help me, thank you!
how we can notify view model of any changes of sensors of android device. I want to find the best practice of notifying changes in clean architecture way. I think about bus event and broadcast receiver.
By default detekt does not check kotlin files within buildSrc
folder.
Theres is existing feature request for detekt, to support it out of the box, but for now we need a custom config:
detekt/detekt#1950 (comment)
BTW
Testing - just break code formating in any of the kotlin files located in the buildSrc\src\main\kotlin
folder (eg. add multiple empty lines at the end of file or redundant spaces) - make PR. If checks fails - it means that detekt
checks buildSrc
folder.
...
com.igorwojda.showcase E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.igorwojda.showcase, PID: 9269
java.lang.ExceptionInInitializerError
at com.igorwojda.showcase.app.ShowcaseApplication$kodein$1.invoke(ShowcaseApplication.kt:25)
at com.igorwojda.showcase.app.ShowcaseApplication$kodein$1.invoke(ShowcaseApplication.kt:20)
at org.kodein.di.internal.KodeinImpl$Companion.newBuilder(KodeinImpl.kt:22)
at org.kodein.di.internal.KodeinImpl$Companion.access$newBuilder(KodeinImpl.kt:21)
at org.kodein.di.internal.KodeinImpl.(KodeinImpl.kt:19)
at org.kodein.di.Kodein$Companion$lazy$1.invoke(Kodein.kt:447)
at org.kodein.di.Kodein$Companion$lazy$1.invoke(Kodein.kt:429)
at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
at org.kodein.di.LazyKodein.getBaseKodein(Unknown Source:2)
at org.kodein.di.LazyKodein.getContainer(lateinit.kt:31)
at org.kodein.di.internal.KodeinMainBuilderImpl.extend(KodeinBuilderImpl.kt:80)
at org.kodein.di.Kodein$MainBuilder$DefaultImpls.extend$default(Kodein.kt:384)
at com.igorwojda.showcase.library.base.presentation.activity.InjectionActivity$kodein$2.invoke(InjectionActivity.kt:24)
at com.igorwojda.showcase.library.base.presentation.activity.InjectionActivity$kodein$2.invoke(InjectionActivity.kt:15)
at org.kodein.di.internal.KodeinImpl$Companion.newBuilder(KodeinImpl.kt:22)
at org.kodein.di.internal.KodeinImpl$Companion.access$newBuilder(KodeinImpl.kt:21)
at org.kodein.di.internal.KodeinImpl.(KodeinImpl.kt:19)
at org.kodein.di.Kodein$Companion.invoke(Kodein.kt:438)
at org.kodein.di.android.RetainedKt$retainedKodein$1.invoke(retained.kt:34)
at org.kodein.di.android.RetainedKt$retainedKodein$1.invoke(Unknown Source:0)
at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
at com.igorwojda.showcase.library.base.presentation.activity.InjectionActivity.getKodein(Unknown Source:2)
at org.kodein.di.KodeinAwareKt$Instance$1.invoke(KodeinAware.kt:176)
at org.kodein.di.KodeinAwareKt$Instance$1.invoke(Unknown Source:4)
at org.kodein.di.KodeinProperty$provideDelegate$1.invoke(properties.kt:42)
at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
at com.igorwojda.showcase.app.presentation.NavHostActivity.getNavManager(Unknown Source:7)
at com.igorwojda.showcase.app.presentation.NavHostActivity.initNavManager(NavHostActivity.kt:30)
at com.igorwojda.showcase.app.presentation.NavHostActivity.onCreate(NavHostActivity.kt:22)
at android.app.Activity.performCreate(Activity.java:7122)
at android.app.Activity.performCreate(Activity.java:7113)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1220)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2965)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3090)
at android.app.ActivityThread.-wrap11(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1794)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:192)
at android.app.ActivityThread.main(ActivityThread.java:6866)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:549)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:817)
Caused by: java.lang.ClassNotFoundException: Kodein module class not found com.igorwojda.showcase.feature.album.FeatureKodeinModule
at com.igorwojda.showcase.app.feature.FeatureManager.(FeatureManager.kt:19)
at com.igorwojda.showcase.app.ShowcaseApplication$kodein$1.invoke(ShowcaseApplication.kt:25)
at com.igorwojda.showcase.app.ShowcaseApplication$kodein$1.invoke(ShowcaseApplication.kt:20)
at org.kodein.di.internal.KodeinImpl$Companion.newBuilder(KodeinImpl.kt:22)
at org.kodein.di.internal.KodeinImpl$Companion.access$newBuilder(KodeinImpl.kt:21)
at org.kodein.di.internal.KodeinImpl.(KodeinImpl.kt:19)
at org.kodein.di.Kodein$Companion$lazy$1.invoke(Kodein.kt:447)
at org.kodein.di.Kodein$Companion$lazy$1.invoke(Kodein.kt:429)
at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
at org.kodein.di.LazyKodein.getBaseKodein(Unknown Source:2)
at org.kodein.di.LazyKodein.getContainer(lateinit.kt:31)
at org.kodein.di.internal.KodeinMainBuilderImpl.extend(KodeinBuilderImpl.kt:80)
at org.kodein.di.Kodein$MainBuilder$DefaultImpls.extend$default(Kodein.kt:384)
at com.igorwojda.showcase.library.base.presentation.activity.InjectionActivity$kodein$2.invoke(InjectionActivity.kt:24)
at com.igorwojda.showcase.library.base.presentation.activity.InjectionActivity$kodein$2.invoke(InjectionActivity.kt:15)
at org.kodein.di.internal.KodeinImpl$Companion.newBuilder(KodeinImpl.kt:22)
at org.kodein.di.internal.KodeinImpl$Companion.access$newBuilder(KodeinImpl.kt:21)
at org.kodein.di.internal.KodeinImpl.(KodeinImpl.kt:19)
at org.kodein.di.Kodein$Companion.invoke(Kodein.kt:438)
at org.kodein.di.android.RetainedKt$retainedKodein$1.invoke(retained.kt:34)
at org.kodein.di.android.RetainedKt$retainedKodein$1.invoke(Unknown Source:0)
at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
at com.igorwojda.showcase.library.base.presentation.activity.InjectionActivity.getKodein(Unknown Source:2)
at org.kodein.di.KodeinAwareKt$Instance$1.invoke(KodeinAware.kt:176)
at org.kodein.di.KodeinAwareKt$Instance$1.invoke(Unknown Source:4)
at org.kodein.di.KodeinProperty$provideDelegate$1.invoke(properties.kt:42)
at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
at com.igorwojda.showcase.app.presentation.NavHostActivity.getNavManager(Unknown Source:7)
at com.igorwojda.showcase.app.presentation.NavHostActivity.initNavManager(NavHostActivity.kt:30)
at com.igorwojda.showcase.app.presentation.NavHostActivity.onCreate(NavHostActivity.kt:22)
at android.app.Activity.performCreate(Activity.java:7122)
at android.app.Activity.performCreate(Activity.java:7113)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1220)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2965)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3090)
at android.app.ActivityThread.-wrap11(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1794)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:192)
at android.app.ActivityThread.main(ActivityThread.java:6866)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:549)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:817)
We could add some UI test to the project (Espresso?)
eg. basic tests like
Some elements within android
block are duplicated in each module. Ideally this configuration should be applied only once (and work all the times, even for future modules).
android {
compileSdkVersion(AndroidConfig.COMPILE_SDK_VERSION)
defaultConfig {
minSdkVersion(AndroidConfig.MIN_SDK_VERSION)
targetSdkVersion(AndroidConfig.TARGET_SDK_VERSION)
versionCode = AndroidConfig.VERSION_CODE
versionName = AndroidConfig.VERSION_NAME
testInstrumentationRunner = AndroidConfig.TEST_INSTRUMENTATION_RUNNER
}
...
Add dependency locking mechanism via Gradle core feature
App crashes when you click on two albums at the same time
I think this project is great as a showcase. However, I am having difficulty adding a module. I tried copy/pasting/refactoring an existing module and I also tried the various options under: File > New >New Module. Can you explain how to add a new module or add it to the README? Also, I am willing to contribute this knowledge to the README if I could figure it out.
Hi @igorwojda you're project is probably the most complete sample around!
Do you plan to add android UI tests?
I'm currently finding difficulties testing the UI with dynamic Feature Modules(with espresso) and was hoping to find something to reference.
Thanks,
Gabriele.
Hi igor
As you explain in Data Flow section in image you show that in Album List Screen user pressed a button, then you delegate it to viewmodel
but again in the same screen you wrote:
albumAdapter.setOnDebouncedClickListener {
val navDirections = AlbumListFragmentDirections
.actionAlbumListToAlbumDetail(it.artist, it.name, it.mbId)
findNavController().navigate(navDirections)
}
Shouldn't it be viewmodels's responsibility? why view decide to navigate to where instead of delegating it to viewmodel and the viewmodel decide to go where by notifying view?
compileOptions
and kotlinOptions
block are duplicated in each module. Ideally this configuration should be applied only once (and work all the times, even for future modules).
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
We could use newer version of JUnit
https://github.com/mannodermaus/android-junit5
Hi @igorwojda ,
I'm trying to create debug APK using the AS option - Build bundle -> Build APK
The build succeeded, but when I'm trying to install the APK to device, it's crashes on some class not found errors...
Is it working for you, when you install the APK using the generated APK ?
Thanks,
Ronny.
The bottom navigation keep reload the fragments on each click, even if it is the same page.
For example, when I'm on home page and I click the home button, the page reload again.
Try to turn Wifi/Mobile Data off and open the app crashes. I have checked the logs but i can't see any specific line of code that might causes this problem
感觉很厉害的样子 但是我看不懂怎么办?有没有中文介绍?
Following UI tests implementation (#85 - ready to grab
) we should tun UI tests in the pipeline on multiple versions of Android API (5-11).
My project cannot be import buildSrc
It would be nice to convert one of the module to on-demand-delivery so it will be downloaded when it is needed
https://developer.android.com/guide/app-bundle/on-demand-delivery
Additionally we could have custom Progress fragment
https://developer.android.com/guide/navigation/navigation-dynamic#customize
Is this comment correct?
I think Delegates.observable
do nothing with deduplication. Per documentation:
And implementation:
It just calls the callback after setting the new value (the new value could be the same).
I just wonder when I have a login activity in my App, I should place the login & register activity inside the APP module or separately has a module with presentation/domain/data for login & register process? Thanks a lot.
I clone the repo and try to run it then it shows
Installation did not succeed.
The application could not be installed: INSTALL_PARSE_FAILED_MANIFEST_EMPTY
List of apks:
[0] 'D:\AndroidStudioProjects\SampleKotlinBase\android-showcase\app\build\outputs\apk\debug\app-debug.apk'
[1] 'D:\AndroidStudioProjects\SampleKotlinBase\android-showcase\feature_album\build\outputs\apk\debug\feature_album-debug.apk'
[2] 'D:\AndroidStudioProjects\SampleKotlinBase\android-showcase\feature_favourite\build\outputs\apk\debug\feature_favourite-debug.apk'
[3] 'D:\AndroidStudioProjects\SampleKotlinBase\android-showcase\feature_profile\build\outputs\apk\debug\feature_profile-debug.apk'
Installation failed due to: 'null'
may be I miss something.
Android Studio 4.
Hello,
thank you for this repo. Why do you have duplicate plugin id's? Specifically in settings.gradle.kts and in build.gradle.kts:
plugins { id(GradlePluginId.DETEKT) version GradlePluginVersion.DETEKT
I just wonder when I have a login activity in my App, I should place the splash activity inside the APP module or separately has a module with presentation/domain/data for splash process? Thanks a lot.
A description of a demo search would be useful. The app launches with "search album" tag, but it's not clear whether it's a music or photo album.
Having searched for adele
, the list did not populate with any results, yet there appear items clickable, which launch a new screen with images missing. This is potentially a separate issue.
We could write custom detekt rule.
It can be any rule, but one example that comes to my head is rule that verifies clean architecture layers (verify dependency rule
correctness) - make sure that class defined in ...domain
layer does not uses any classes from other layers (have imports from other layers).
Another idea would be the rule to Verify that every class that extends androidx.lifecycle.ViewModel
class has ViewModel
suffix
Also maybe we could also add tests for this rules 🤔
More:
https://arturbosch.github.io/detekt/extensions.html
https://proandroiddev.com/writing-custom-lint-rules-for-your-kotlin-project-with-detekt-653e4dbbe8b9
https://medium.com/@vanniktech/writing-your-first-detekt-rule-ee940e56428d
Hi Igor, I am a follower of your best practices and I am trying to implement them in my own projects.
My problem is that I have to make 6-8 request in the same view. At the begining I was using the same stateLiveData to display all the lists but it was not working really well and now I have created multiple Observables in my viewmodel but I cannot use the same BaseViewModel as I have to add differents ViewActions and ViewStates as type arguments, also 400 new lines of code spread between classes.
So my question is: Do you have an example on how to do this? Is there a clean solution? My app is working well this way but I feel myself that I am desecrating your code.
Edit: I am using the same instance of stateTimeTravellerDebugger in BaseViewAction. When I have many ViewStates and ViewActions, I have noticed that sometimes when the request are arriving at the same time, stateTimetravellerDebbuger lastViewAction is null. Maybe what I am trying to do is not the best, should I create one logger for any ViewState I have?.
Many Thanks.
Stetho/Flipper are great tools for looking inside your Android/iOS app. But they are not tools we should leave in the app when we build a Release. Show through additional modules how to remove Stetho/Flipper on Release builds.
I am happy to raise a PR for this
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.