├── app ├── .gitignore ├── src │ └── main │ │ ├── res │ │ ├── drawable-xxhdpi │ │ │ ├── octocat.png │ │ │ ├── placeholder.png │ │ │ └── ic_search_white_36dp.png │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ ├── drawable-hdpi │ │ │ └── ic_search_white_36dp.png │ │ ├── drawable-mdpi │ │ │ └── ic_search_white_36dp.png │ │ ├── drawable-xhdpi │ │ │ └── ic_search_white_36dp.png │ │ ├── drawable-xxxhdpi │ │ │ └── ic_search_white_36dp.png │ │ ├── values │ │ │ ├── dimens.xml │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── layout │ │ │ ├── item_repo.xml │ │ │ └── activity_main.xml │ │ ├── java │ │ └── uk │ │ │ └── ivanc │ │ │ └── archi │ │ │ ├── model │ │ │ ├── GithubService.java │ │ │ └── User.java │ │ │ ├── ArchiApplication.java │ │ │ └── RepositoryAdapter.java │ │ └── AndroidManifest.xml ├── proguard-rules.pro └── build.gradle ├── kapp ├── .gitignore ├── src │ └── main │ │ ├── res │ │ ├── drawable-xxhdpi │ │ │ ├── octocat.png │ │ │ ├── placeholder.png │ │ │ └── ic_search_white_36dp.png │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ ├── drawable-hdpi │ │ │ └── ic_search_white_36dp.png │ │ ├── drawable-mdpi │ │ │ └── ic_search_white_36dp.png │ │ ├── drawable-xhdpi │ │ │ └── ic_search_white_36dp.png │ │ ├── drawable-xxxhdpi │ │ │ └── ic_search_white_36dp.png │ │ ├── values │ │ │ ├── dimens.xml │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── layout │ │ │ ├── item_repo.xml │ │ │ └── activity_main.xml │ │ ├── java │ │ └── com │ │ │ └── taishi │ │ │ └── kapp │ │ │ ├── model │ │ │ ├── GithubService.kt │ │ │ └── User.kt │ │ │ ├── ArchiApplication.kt │ │ │ ├── RepositoryAdapter.kt │ │ │ └── RepositoryActivity.kt │ │ └── AndroidManifest.xml ├── proguard-rules.pro └── build.gradle ├── app-mvp ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── drawable-xxhdpi │ │ │ │ ├── octocat.png │ │ │ │ ├── placeholder.png │ │ │ │ └── ic_search_white_36dp.png │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── drawable-hdpi │ │ │ │ └── ic_search_white_36dp.png │ │ │ ├── drawable-mdpi │ │ │ │ └── ic_search_white_36dp.png │ │ │ ├── drawable-xhdpi │ │ │ │ └── ic_search_white_36dp.png │ │ │ ├── drawable-xxxhdpi │ │ │ │ └── ic_search_white_36dp.png │ │ │ ├── values │ │ │ │ ├── dimens.xml │ │ │ │ ├── colors.xml │ │ │ │ ├── strings.xml │ │ │ │ └── styles.xml │ │ │ └── layout │ │ │ │ ├── item_repo.xml │ │ │ │ └── activity_main.xml │ │ ├── java │ │ │ └── uk │ │ │ │ └── ivanc │ │ │ │ └── archimvp │ │ │ │ ├── view │ │ │ │ ├── MvpView.java │ │ │ │ ├── RepositoryMvpView.java │ │ │ │ └── MainMvpView.java │ │ │ │ ├── presenter │ │ │ │ ├── Presenter.java │ │ │ │ ├── RepositoryPresenter.java │ │ │ │ └── MainPresenter.java │ │ │ │ ├── model │ │ │ │ ├── GithubService.java │ │ │ │ └── User.java │ │ │ │ ├── ArchiApplication.java │ │ │ │ └── RepositoryAdapter.java │ │ └── AndroidManifest.xml │ └── test │ │ └── java │ │ └── uk │ │ └── ivanc │ │ └── archimvp │ │ ├── util │ │ └── MockModelFabric.java │ │ └── RepositoryPresenterTest.java ├── proguard-rules.pro └── build.gradle ├── app-mvvm ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── drawable-xxhdpi │ │ │ │ ├── octocat.png │ │ │ │ ├── placeholder.png │ │ │ │ └── ic_search_white_36dp.png │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── drawable-hdpi │ │ │ │ └── ic_search_white_36dp.png │ │ │ ├── drawable-mdpi │ │ │ │ └── ic_search_white_36dp.png │ │ │ ├── drawable-xhdpi │ │ │ │ └── ic_search_white_36dp.png │ │ │ ├── drawable-xxxhdpi │ │ │ │ └── ic_search_white_36dp.png │ │ │ └── values │ │ │ │ ├── dimens.xml │ │ │ │ ├── colors.xml │ │ │ │ ├── strings.xml │ │ │ │ └── styles.xml │ │ ├── java │ │ │ └── uk │ │ │ │ └── ivanc │ │ │ │ └── archimvvm │ │ │ │ ├── viewmodel │ │ │ │ ├── ViewModel.java │ │ │ │ └── ItemRepoViewModel.java │ │ │ │ ├── model │ │ │ │ ├── GithubService.java │ │ │ │ └── User.java │ │ │ │ ├── ArchiApplication.java │ │ │ │ ├── view │ │ │ │ ├── RepositoryActivity.java │ │ │ │ └── MainActivity.java │ │ │ │ └── RepositoryAdapter.java │ │ └── AndroidManifest.xml │ └── test │ │ └── java │ │ └── uk │ │ └── ivanc │ │ └── archimvvm │ │ ├── util │ │ └── MockModelFabric.java │ │ ├── RepositoryViewModelTest.java │ │ └── ItemRepoViewModelTest.java ├── proguard-rules.pro └── build.gradle ├── kapp-mvp ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── drawable-xxhdpi │ │ │ │ ├── octocat.png │ │ │ │ ├── placeholder.png │ │ │ │ └── ic_search_white_36dp.png │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── drawable-hdpi │ │ │ │ └── ic_search_white_36dp.png │ │ │ ├── drawable-mdpi │ │ │ │ └── ic_search_white_36dp.png │ │ │ ├── drawable-xhdpi │ │ │ │ └── ic_search_white_36dp.png │ │ │ ├── drawable-xxxhdpi │ │ │ │ └── ic_search_white_36dp.png │ │ │ ├── values │ │ │ │ ├── dimens.xml │ │ │ │ ├── colors.xml │ │ │ │ ├── strings.xml │ │ │ │ └── styles.xml │ │ │ └── layout │ │ │ │ ├── item_repo.xml │ │ │ │ └── activity_main.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── taishi │ │ │ │ └── kapp_mvp │ │ │ │ ├── view │ │ │ │ ├── MvpView.kt │ │ │ │ ├── RepositoryMvpView.kt │ │ │ │ └── MainMvpView.kt │ │ │ │ ├── presenter │ │ │ │ ├── Presenter.kt │ │ │ │ ├── RepositoryPresenter.kt │ │ │ │ └── MainPresenter.kt │ │ │ │ ├── GithubService.kt │ │ │ │ ├── ArchiApplication.kt │ │ │ │ ├── RepositoryAdapter.kt │ │ │ │ └── User.kt │ │ └── AndroidManifest.xml │ └── test │ │ └── java │ │ └── com │ │ └── taishi │ │ └── kapp_mvp │ │ ├── util │ │ └── MockModelFabric.java │ │ ├── RepositoryPresenterTest.java │ │ └── MainPresenterTest.java ├── proguard-rules.pro └── build.gradle ├── kapp-mvvm ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── drawable-xxhdpi │ │ │ │ ├── octocat.png │ │ │ │ ├── placeholder.png │ │ │ │ └── ic_search_white_36dp.png │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── drawable-hdpi │ │ │ │ └── ic_search_white_36dp.png │ │ │ ├── drawable-mdpi │ │ │ │ └── ic_search_white_36dp.png │ │ │ ├── drawable-xhdpi │ │ │ │ └── ic_search_white_36dp.png │ │ │ ├── drawable-xxxhdpi │ │ │ │ └── ic_search_white_36dp.png │ │ │ └── values │ │ │ │ ├── dimens.xml │ │ │ │ ├── colors.xml │ │ │ │ ├── strings.xml │ │ │ │ └── styles.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── taishi │ │ │ │ └── kapp_mvvm │ │ │ │ ├── viewmodel │ │ │ │ ├── ViewModel.kt │ │ │ │ └── ItemRepoViewModel.kt │ │ │ │ ├── model │ │ │ │ ├── GithubService.kt │ │ │ │ └── User.kt │ │ │ │ ├── ArchiApplication.kt │ │ │ │ ├── view │ │ │ │ ├── RepositoryActivity.kt │ │ │ │ └── MainActivity.kt │ │ │ │ └── RepositoryAdapter.kt │ │ └── AndroidManifest.xml │ └── test │ │ └── java │ │ └── com │ │ └── taishi │ │ └── kapp_mvvm │ │ ├── ExampleUnitTest.java │ │ ├── MockModelFabric.java │ │ ├── RepositoryViewModelTest.java │ │ └── ItemRepoViewModelTest.java ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── images └── archi-screenshots.png ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .idea ├── runConfigurations.xml └── gradle.xml ├── .gitignore ├── gradle.properties ├── dependencies.gradle ├── gradlew.bat └── README.md /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /kapp/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app-mvp/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app-mvvm/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /kapp-mvp/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /kapp-mvvm/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':app-mvp', ':app-mvvm', ':kapp', ':kapp-mvp', ':kapp-mvvm' 2 | -------------------------------------------------------------------------------- /images/archi-screenshots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/images/archi-screenshots.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/octocat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/app/src/main/res/drawable-xxhdpi/octocat.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /kapp/src/main/res/drawable-xxhdpi/octocat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/kapp/src/main/res/drawable-xxhdpi/octocat.png -------------------------------------------------------------------------------- /kapp/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/kapp/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /kapp/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/kapp/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /kapp/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/kapp/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app-mvp/src/main/res/drawable-xxhdpi/octocat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/app-mvp/src/main/res/drawable-xxhdpi/octocat.png -------------------------------------------------------------------------------- /app-mvp/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/app-mvp/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app-mvp/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/app-mvp/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/app/src/main/res/drawable-xxhdpi/placeholder.png -------------------------------------------------------------------------------- /kapp/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/kapp/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app-mvp/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/app-mvp/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app-mvp/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/app-mvp/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app-mvvm/src/main/res/drawable-xxhdpi/octocat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/app-mvvm/src/main/res/drawable-xxhdpi/octocat.png -------------------------------------------------------------------------------- /app-mvvm/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/app-mvvm/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app-mvvm/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/app-mvvm/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app-mvvm/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/app-mvvm/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app-mvvm/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/app-mvvm/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /kapp-mvp/src/main/res/drawable-xxhdpi/octocat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/kapp-mvp/src/main/res/drawable-xxhdpi/octocat.png -------------------------------------------------------------------------------- /kapp-mvp/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/kapp-mvp/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /kapp-mvp/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/kapp-mvp/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /kapp-mvp/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/kapp-mvp/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /kapp-mvp/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/kapp-mvp/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /kapp-mvvm/src/main/res/drawable-xxhdpi/octocat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/kapp-mvvm/src/main/res/drawable-xxhdpi/octocat.png -------------------------------------------------------------------------------- /kapp-mvvm/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/kapp-mvvm/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /kapp-mvvm/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/kapp-mvvm/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /kapp-mvvm/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/kapp-mvvm/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /kapp/src/main/res/drawable-xxhdpi/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/kapp/src/main/res/drawable-xxhdpi/placeholder.png -------------------------------------------------------------------------------- /app-mvp/src/main/res/drawable-xxhdpi/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/app-mvp/src/main/res/drawable-xxhdpi/placeholder.png -------------------------------------------------------------------------------- /app-mvvm/src/main/res/drawable-xxhdpi/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/app-mvvm/src/main/res/drawable-xxhdpi/placeholder.png -------------------------------------------------------------------------------- /kapp-mvp/src/main/res/drawable-xxhdpi/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/kapp-mvp/src/main/res/drawable-xxhdpi/placeholder.png -------------------------------------------------------------------------------- /kapp-mvvm/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/kapp-mvvm/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_search_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/app/src/main/res/drawable-hdpi/ic_search_white_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_search_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/app/src/main/res/drawable-mdpi/ic_search_white_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_search_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/app/src/main/res/drawable-xhdpi/ic_search_white_36dp.png -------------------------------------------------------------------------------- /kapp-mvvm/src/main/res/drawable-xxhdpi/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/kapp-mvvm/src/main/res/drawable-xxhdpi/placeholder.png -------------------------------------------------------------------------------- /kapp/src/main/res/drawable-hdpi/ic_search_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/kapp/src/main/res/drawable-hdpi/ic_search_white_36dp.png -------------------------------------------------------------------------------- /kapp/src/main/res/drawable-mdpi/ic_search_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/kapp/src/main/res/drawable-mdpi/ic_search_white_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_search_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/app/src/main/res/drawable-xxhdpi/ic_search_white_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_search_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/app/src/main/res/drawable-xxxhdpi/ic_search_white_36dp.png -------------------------------------------------------------------------------- /kapp/src/main/res/drawable-xhdpi/ic_search_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/kapp/src/main/res/drawable-xhdpi/ic_search_white_36dp.png -------------------------------------------------------------------------------- /kapp/src/main/res/drawable-xxhdpi/ic_search_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/kapp/src/main/res/drawable-xxhdpi/ic_search_white_36dp.png -------------------------------------------------------------------------------- /app-mvp/src/main/res/drawable-hdpi/ic_search_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/app-mvp/src/main/res/drawable-hdpi/ic_search_white_36dp.png -------------------------------------------------------------------------------- /app-mvp/src/main/res/drawable-mdpi/ic_search_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/app-mvp/src/main/res/drawable-mdpi/ic_search_white_36dp.png -------------------------------------------------------------------------------- /app-mvp/src/main/res/drawable-xhdpi/ic_search_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/app-mvp/src/main/res/drawable-xhdpi/ic_search_white_36dp.png -------------------------------------------------------------------------------- /app-mvp/src/main/res/drawable-xxhdpi/ic_search_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/app-mvp/src/main/res/drawable-xxhdpi/ic_search_white_36dp.png -------------------------------------------------------------------------------- /app-mvvm/src/main/res/drawable-hdpi/ic_search_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/app-mvvm/src/main/res/drawable-hdpi/ic_search_white_36dp.png -------------------------------------------------------------------------------- /app-mvvm/src/main/res/drawable-mdpi/ic_search_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/app-mvvm/src/main/res/drawable-mdpi/ic_search_white_36dp.png -------------------------------------------------------------------------------- /app-mvvm/src/main/res/drawable-xhdpi/ic_search_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/app-mvvm/src/main/res/drawable-xhdpi/ic_search_white_36dp.png -------------------------------------------------------------------------------- /kapp-mvp/src/main/res/drawable-hdpi/ic_search_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/kapp-mvp/src/main/res/drawable-hdpi/ic_search_white_36dp.png -------------------------------------------------------------------------------- /kapp-mvp/src/main/res/drawable-mdpi/ic_search_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/kapp-mvp/src/main/res/drawable-mdpi/ic_search_white_36dp.png -------------------------------------------------------------------------------- /kapp-mvp/src/main/res/drawable-xhdpi/ic_search_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/kapp-mvp/src/main/res/drawable-xhdpi/ic_search_white_36dp.png -------------------------------------------------------------------------------- /kapp-mvvm/src/main/res/drawable-hdpi/ic_search_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/kapp-mvvm/src/main/res/drawable-hdpi/ic_search_white_36dp.png -------------------------------------------------------------------------------- /kapp-mvvm/src/main/res/drawable-mdpi/ic_search_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/kapp-mvvm/src/main/res/drawable-mdpi/ic_search_white_36dp.png -------------------------------------------------------------------------------- /kapp/src/main/res/drawable-xxxhdpi/ic_search_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/kapp/src/main/res/drawable-xxxhdpi/ic_search_white_36dp.png -------------------------------------------------------------------------------- /app-mvp/src/main/res/drawable-xxxhdpi/ic_search_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/app-mvp/src/main/res/drawable-xxxhdpi/ic_search_white_36dp.png -------------------------------------------------------------------------------- /app-mvvm/src/main/res/drawable-xxhdpi/ic_search_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/app-mvvm/src/main/res/drawable-xxhdpi/ic_search_white_36dp.png -------------------------------------------------------------------------------- /app-mvvm/src/main/res/drawable-xxxhdpi/ic_search_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/app-mvvm/src/main/res/drawable-xxxhdpi/ic_search_white_36dp.png -------------------------------------------------------------------------------- /kapp-mvp/src/main/res/drawable-xxhdpi/ic_search_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/kapp-mvp/src/main/res/drawable-xxhdpi/ic_search_white_36dp.png -------------------------------------------------------------------------------- /kapp-mvp/src/main/res/drawable-xxxhdpi/ic_search_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/kapp-mvp/src/main/res/drawable-xxxhdpi/ic_search_white_36dp.png -------------------------------------------------------------------------------- /kapp-mvvm/src/main/res/drawable-xhdpi/ic_search_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/kapp-mvvm/src/main/res/drawable-xhdpi/ic_search_white_36dp.png -------------------------------------------------------------------------------- /kapp-mvvm/src/main/res/drawable-xxhdpi/ic_search_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/kapp-mvvm/src/main/res/drawable-xxhdpi/ic_search_white_36dp.png -------------------------------------------------------------------------------- /kapp-mvvm/src/main/res/drawable-xxxhdpi/ic_search_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Taishi-Y/KArchi/HEAD/kapp-mvvm/src/main/res/drawable-xxxhdpi/ic_search_white_36dp.png -------------------------------------------------------------------------------- /app-mvp/src/main/java/uk/ivanc/archimvp/view/MvpView.java: -------------------------------------------------------------------------------- 1 | package uk.ivanc.archimvp.view; 2 | 3 | import android.content.Context; 4 | 5 | public interface MvpView { 6 | 7 | Context getContext(); 8 | } 9 | -------------------------------------------------------------------------------- /kapp-mvp/src/main/java/com/taishi/kapp_mvp/view/MvpView.kt: -------------------------------------------------------------------------------- 1 | package com.taishi.kapp_mvp.view 2 | 3 | import android.content.Context 4 | 5 | interface MvpView { 6 | 7 | fun getContext(): Context 8 | } 9 | -------------------------------------------------------------------------------- /kapp-mvp/src/main/java/com/taishi/kapp_mvp/presenter/Presenter.kt: -------------------------------------------------------------------------------- 1 | package com.taishi.kapp_mvp.presenter 2 | 3 | interface Presenter { 4 | 5 | fun attachView(view: V) 6 | 7 | fun detachView() 8 | 9 | } 10 | -------------------------------------------------------------------------------- /app-mvp/src/main/java/uk/ivanc/archimvp/presenter/Presenter.java: -------------------------------------------------------------------------------- 1 | package uk.ivanc.archimvp.presenter; 2 | 3 | public interface Presenter { 4 | 5 | void attachView(V view); 6 | 7 | void detachView(); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /kapp-mvvm/src/main/java/com/taishi/kapp_mvvm/viewmodel/ViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.taishi.kapp_mvvm.viewmodel 2 | 3 | /** 4 | * Interface that every ViewModel must implement 5 | */ 6 | interface ViewModel { 7 | 8 | fun destroy() 9 | } 10 | -------------------------------------------------------------------------------- /app-mvvm/src/main/java/uk/ivanc/archimvvm/viewmodel/ViewModel.java: -------------------------------------------------------------------------------- 1 | package uk.ivanc.archimvvm.viewmodel; 2 | 3 | /** 4 | * Interface that every ViewModel must implement 5 | */ 6 | public interface ViewModel { 7 | 8 | void destroy(); 9 | } 10 | -------------------------------------------------------------------------------- /kapp-mvp/src/main/java/com/taishi/kapp_mvp/view/RepositoryMvpView.kt: -------------------------------------------------------------------------------- 1 | package com.taishi.kapp_mvp.view 2 | 3 | 4 | import com.taishi.kapp_mvp.User 5 | 6 | interface RepositoryMvpView : MvpView { 7 | 8 | fun showOwner(owner: User) 9 | 10 | } 11 | -------------------------------------------------------------------------------- /app-mvp/src/main/java/uk/ivanc/archimvp/view/RepositoryMvpView.java: -------------------------------------------------------------------------------- 1 | package uk.ivanc.archimvp.view; 2 | 3 | import uk.ivanc.archimvp.model.User; 4 | 5 | public interface RepositoryMvpView extends MvpView { 6 | 7 | void showOwner(final User owner); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Sep 23 11:02:39 JST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip 7 | -------------------------------------------------------------------------------- /app-mvp/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12dp 4 | 12dp 5 | 6dp 6 | 6dp 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12dp 4 | 12dp 5 | 6dp 6 | 6dp 7 | 8 | -------------------------------------------------------------------------------- /kapp/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12dp 4 | 12dp 5 | 6dp 6 | 6dp 7 | 8 | -------------------------------------------------------------------------------- /app-mvvm/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12dp 4 | 12dp 5 | 6dp 6 | 6dp 7 | 8 | -------------------------------------------------------------------------------- /kapp-mvp/src/main/java/com/taishi/kapp_mvp/view/MainMvpView.kt: -------------------------------------------------------------------------------- 1 | package com.taishi.kapp_mvp.view 2 | 3 | import com.taishi.kapp_mvp.Repository 4 | 5 | 6 | interface MainMvpView : MvpView { 7 | 8 | fun showRepositories(repositories: List) 9 | 10 | fun showMessage(stringId: Int) 11 | 12 | fun showProgressIndicator() 13 | } 14 | -------------------------------------------------------------------------------- /kapp-mvp/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12dp 4 | 12dp 5 | 6dp 6 | 6dp 7 | 8 | -------------------------------------------------------------------------------- /kapp-mvvm/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12dp 4 | 12dp 5 | 6dp 6 | 6dp 7 | 8 | -------------------------------------------------------------------------------- /app-mvp/src/main/java/uk/ivanc/archimvp/view/MainMvpView.java: -------------------------------------------------------------------------------- 1 | package uk.ivanc.archimvp.view; 2 | 3 | import java.util.List; 4 | 5 | import uk.ivanc.archimvp.model.Repository; 6 | 7 | public interface MainMvpView extends MvpView { 8 | 9 | void showRepositories(List repositories); 10 | 11 | void showMessage(int stringId); 12 | 13 | void showProgressIndicator(); 14 | } 15 | -------------------------------------------------------------------------------- /kapp-mvvm/src/test/java/com/taishi/kapp_mvvm/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.taishi.kapp_mvvm; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.assertEquals; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #000000 4 | #ffffff 5 | #75ffffff 6 | #e1e1e1 7 | #3F51B5 8 | #303F9F 9 | #C5CAE9 10 | #03A9F4 11 | #212121 12 | #727272 13 | #FFFFFF 14 | #cbcbcb 15 | -------------------------------------------------------------------------------- /app-mvp/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #000000 4 | #ffffff 5 | #75ffffff 6 | #e1e1e1 7 | #3F51B5 8 | #303F9F 9 | #C5CAE9 10 | #03A9F4 11 | #212121 12 | #727272 13 | #FFFFFF 14 | #cbcbcb 15 | -------------------------------------------------------------------------------- /app-mvvm/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #000000 4 | #ffffff 5 | #75ffffff 6 | #e1e1e1 7 | #3F51B5 8 | #303F9F 9 | #C5CAE9 10 | #03A9F4 11 | #212121 12 | #727272 13 | #FFFFFF 14 | #cbcbcb 15 | -------------------------------------------------------------------------------- /kapp-mvp/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #000000 4 | #ffffff 5 | #75ffffff 6 | #e1e1e1 7 | #3F51B5 8 | #303F9F 9 | #C5CAE9 10 | #03A9F4 11 | #212121 12 | #727272 13 | #FFFFFF 14 | #cbcbcb 15 | -------------------------------------------------------------------------------- /kapp/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #000000 4 | #ffffff 5 | #75ffffff 6 | #e1e1e1 7 | #3F51B5 8 | #303F9F 9 | #C5CAE9 10 | #03A9F4 11 | #212121 12 | #727272 13 | #FFFFFF 14 | #cbcbcb 15 | -------------------------------------------------------------------------------- /kapp-mvvm/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #000000 4 | #ffffff 5 | #75ffffff 6 | #e1e1e1 7 | #3F51B5 8 | #303F9F 9 | #C5CAE9 10 | #03A9F4 11 | #212121 12 | #727272 13 | #FFFFFF 14 | #cbcbcb 15 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/ivan/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app-mvp/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/ivan/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app-mvvm/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/ivan/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | /*/build/ 3 | 4 | # Crashlytics configuations 5 | com_crashlytics_export_strings.xml 6 | 7 | # Local configuration file (sdk path, etc) 8 | local.properties 9 | 10 | # Gradle generated files 11 | .gradle/ 12 | 13 | # Signing files 14 | .signing/ 15 | 16 | # User-specific configurations 17 | .idea/libraries/ 18 | .idea/workspace.xml 19 | .idea/tasks.xml 20 | .idea/.name 21 | .idea/compiler.xml 22 | .idea/copyright/profiles_settings.xml 23 | .idea/encodings.xml 24 | .idea/misc.xml 25 | .idea/modules.xml 26 | .idea/scopes/scope_settings.xml 27 | .idea/vcs.xml 28 | *.iml 29 | 30 | # OS-specific files 31 | .DS_Store 32 | .DS_Store? 33 | ._* 34 | .Spotlight-V100 35 | .Trashes 36 | ehthumbs.db 37 | Thumbs.db 38 | 39 | .idea/gradle.xml 40 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 21 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Archi 3 | 4 | Hello world! 5 | Settings 6 | %d \nStars 7 | %d \nWatchers 8 | %d \nForks 9 | Oops, something went wrong 10 | This account doesn\'t have any public repository 11 | Oops, Octocat doesn\'t know that username 12 | GitHub username 13 | Enter a GitHub username above to see its repositories 14 | This repository is a fork 15 | Language: %s 16 | 17 | -------------------------------------------------------------------------------- /kapp/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | KArchi 3 | 4 | Hello world! 5 | Settings 6 | %d \nStars 7 | %d \nWatchers 8 | %d \nForks 9 | Oops, something went wrong 10 | This account doesn\'t have any public repository 11 | Oops, Octocat doesn\'t know that username 12 | GitHub username 13 | Enter a GitHub username above to see its repositories 14 | This repository is a fork 15 | Language: %s 16 | 17 | -------------------------------------------------------------------------------- /app-mvp/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Archi MVP 3 | 4 | Hello world! 5 | Settings 6 | %d \nStars 7 | %d \nWatchers 8 | %d \nForks 9 | Oops, something went wrong 10 | This account doesn\'t have any public repository 11 | Oops, Octocat doesn\'t know that username 12 | GitHub username 13 | Enter a GitHub username above to see its repositories 14 | This repository is a fork 15 | Language: %s 16 | 17 | -------------------------------------------------------------------------------- /app-mvvm/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Archi MVVM 3 | 4 | Hello world! 5 | Settings 6 | %d \nStars 7 | %d \nWatchers 8 | %d \nForks 9 | Oops, something went wrong 10 | This account doesn\'t have any public repository 11 | Oops, Octocat doesn\'t know that username 12 | GitHub username 13 | Enter a GitHub username above to see its repositories 14 | This repository is a fork 15 | Language: %s 16 | 17 | -------------------------------------------------------------------------------- /kapp-mvp/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | KArchi MVP 3 | 4 | Hello world! 5 | Settings 6 | %d \nStars 7 | %d \nWatchers 8 | %d \nForks 9 | Oops, something went wrong 10 | This account doesn\'t have any public repository 11 | Oops, Octocat doesn\'t know that username 12 | GitHub username 13 | Enter a GitHub username above to see its repositories 14 | This repository is a fork 15 | Language: %s 16 | 17 | -------------------------------------------------------------------------------- /kapp-mvvm/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Archi MVVM 3 | 4 | Hello world! 5 | Settings 6 | %d \nStars 7 | %d \nWatchers 8 | %d \nForks 9 | Oops, something went wrong 10 | This account doesn\'t have any public repository 11 | Oops, Octocat doesn\'t know that username 12 | GitHub username 13 | Enter a GitHub username above to see its repositories 14 | This repository is a fork 15 | Language: %s 16 | 17 | -------------------------------------------------------------------------------- /dependencies.gradle: -------------------------------------------------------------------------------- 1 | ext { 2 | supportLibraryVersion = '25.1.1' 3 | retrofitVersion = '2.1.0' 4 | 5 | dependencies = [ 6 | appCompat: "com.android.support:appcompat-v7:$supportLibraryVersion", 7 | cardView: "com.android.support:cardview-v7:$supportLibraryVersion", 8 | recyclerView: "com.android.support:recyclerview-v7:$supportLibraryVersion", 9 | retrofit: "com.squareup.retrofit2:retrofit:$retrofitVersion", 10 | retrofitConverterGson: "com.squareup.retrofit2:converter-gson:$retrofitVersion", 11 | retrofitAdapterRxJava: "com.squareup.retrofit2:adapter-rxjava:$retrofitVersion", 12 | picasso: 'com.squareup.picasso:picasso:2.5.2', 13 | rxAndroid: 'io.reactivex:rxandroid:1.2.1', 14 | circleImageView: 'de.hdodenhof:circleimageview:1.3.0', 15 | jUnit: 'junit:junit:4.12', 16 | mockito: 'org.mockito:mockito-core:1.10.19', 17 | robolectric: 'org.robolectric:robolectric:3.1.2' 18 | ] 19 | } -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 15 | 16 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /kapp/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 15 | 16 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app-mvp/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 15 | 16 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app-mvvm/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 15 | 16 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /kapp-mvp/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 15 | 16 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /kapp-mvvm/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 15 | 16 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /kapp/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/yamasakitaishi/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /kapp-mvp/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/yamasakitaishi/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /kapp-mvvm/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/yamasakitaishi/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /kapp-mvp/src/main/java/com/taishi/kapp_mvp/GithubService.kt: -------------------------------------------------------------------------------- 1 | package com.taishi.kapp_mvp 2 | 3 | import retrofit2.Retrofit 4 | import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory 5 | import retrofit2.converter.gson.GsonConverterFactory 6 | import retrofit2.http.GET 7 | import retrofit2.http.Path 8 | import retrofit2.http.Url 9 | import rx.Observable 10 | 11 | interface GithubService { 12 | 13 | @GET("users/{username}/repos") 14 | fun publicRepositories(@Path("username") username: String): Observable> 15 | 16 | @GET 17 | fun userFromUrl(@Url userUrl: String): Observable 18 | 19 | 20 | object Factory { 21 | fun create(): GithubService { 22 | val retrofit = Retrofit.Builder() 23 | .baseUrl("https://api.github.com/") 24 | .addConverterFactory(GsonConverterFactory.create()) 25 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 26 | .build() 27 | return retrofit.create(GithubService::class.java) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /kapp/src/main/java/com/taishi/kapp/model/GithubService.kt: -------------------------------------------------------------------------------- 1 | package com.taishi.kapp.model 2 | 3 | import retrofit2.Retrofit 4 | import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory 5 | import retrofit2.converter.gson.GsonConverterFactory 6 | import retrofit2.http.GET 7 | import retrofit2.http.Path 8 | import retrofit2.http.Url 9 | import rx.Observable 10 | 11 | interface GithubService { 12 | 13 | @GET("users/{username}/repos") 14 | fun publicRepositories(@Path("username") username: String): Observable> 15 | 16 | @GET 17 | fun userFromUrl(@Url userUrl: String): Observable 18 | 19 | 20 | object Factory { 21 | fun create(): GithubService { 22 | val retrofit = Retrofit.Builder() 23 | .baseUrl("https://api.github.com/") 24 | .addConverterFactory(GsonConverterFactory.create()) 25 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 26 | .build() 27 | return retrofit.create(GithubService::class.java) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /kapp-mvvm/src/main/java/com/taishi/kapp_mvvm/model/GithubService.kt: -------------------------------------------------------------------------------- 1 | package com.taishi.kapp_mvvm.model 2 | 3 | import retrofit2.Retrofit 4 | import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory 5 | import retrofit2.converter.gson.GsonConverterFactory 6 | import retrofit2.http.GET 7 | import retrofit2.http.Path 8 | import retrofit2.http.Url 9 | import rx.Observable 10 | 11 | interface GithubService { 12 | 13 | @GET("users/{username}/repos") 14 | fun publicRepositories(@Path("username") username: String): Observable> 15 | 16 | @GET 17 | fun userFromUrl(@Url userUrl: String): Observable 18 | 19 | 20 | object Factory { 21 | fun create(): GithubService { 22 | val retrofit = Retrofit.Builder() 23 | .baseUrl("https://api.github.com/") 24 | .addConverterFactory(GsonConverterFactory.create()) 25 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 26 | .build() 27 | return retrofit.create(GithubService::class.java) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/uk/ivanc/archi/model/GithubService.java: -------------------------------------------------------------------------------- 1 | package uk.ivanc.archi.model; 2 | 3 | import java.util.List; 4 | 5 | import retrofit2.Retrofit; 6 | import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; 7 | import retrofit2.converter.gson.GsonConverterFactory; 8 | import retrofit2.http.GET; 9 | import retrofit2.http.Path; 10 | import retrofit2.http.Url; 11 | import rx.Observable; 12 | 13 | public interface GithubService { 14 | 15 | @GET("users/{username}/repos") 16 | Observable> publicRepositories(@Path("username") String username); 17 | 18 | @GET 19 | Observable userFromUrl(@Url String userUrl); 20 | 21 | 22 | class Factory { 23 | public static GithubService create() { 24 | Retrofit retrofit = new Retrofit.Builder() 25 | .baseUrl("https://api.github.com/") 26 | .addConverterFactory(GsonConverterFactory.create()) 27 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 28 | .build(); 29 | return retrofit.create(GithubService.class); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app-mvp/src/main/java/uk/ivanc/archimvp/model/GithubService.java: -------------------------------------------------------------------------------- 1 | package uk.ivanc.archimvp.model; 2 | 3 | import java.util.List; 4 | 5 | import retrofit2.Retrofit; 6 | import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; 7 | import retrofit2.converter.gson.GsonConverterFactory; 8 | import retrofit2.http.GET; 9 | import retrofit2.http.Path; 10 | import retrofit2.http.Url; 11 | import rx.Observable; 12 | 13 | public interface GithubService { 14 | 15 | @GET("users/{username}/repos") 16 | Observable> publicRepositories(@Path("username") String username); 17 | 18 | @GET 19 | Observable userFromUrl(@Url String userUrl); 20 | 21 | 22 | class Factory { 23 | public static GithubService create() { 24 | Retrofit retrofit = new Retrofit.Builder() 25 | .baseUrl("https://api.github.com/") 26 | .addConverterFactory(GsonConverterFactory.create()) 27 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 28 | .build(); 29 | return retrofit.create(GithubService.class); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app-mvvm/src/main/java/uk/ivanc/archimvvm/model/GithubService.java: -------------------------------------------------------------------------------- 1 | package uk.ivanc.archimvvm.model; 2 | 3 | import java.util.List; 4 | 5 | import retrofit2.Retrofit; 6 | import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; 7 | import retrofit2.converter.gson.GsonConverterFactory; 8 | import retrofit2.http.GET; 9 | import retrofit2.http.Path; 10 | import retrofit2.http.Url; 11 | import rx.Observable; 12 | 13 | public interface GithubService { 14 | 15 | @GET("users/{username}/repos") 16 | Observable> publicRepositories(@Path("username") String username); 17 | 18 | @GET 19 | Observable userFromUrl(@Url String userUrl); 20 | 21 | 22 | class Factory { 23 | public static GithubService create() { 24 | Retrofit retrofit = new Retrofit.Builder() 25 | .baseUrl("https://api.github.com/") 26 | .addConverterFactory(GsonConverterFactory.create()) 27 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 28 | .build(); 29 | return retrofit.create(GithubService.class); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion rootProject.ext.androidCompileSdkVersion 5 | buildToolsVersion rootProject.ext.androidBuildToolsVersion 6 | 7 | defaultConfig { 8 | applicationId "uk.ivanc.archi" 9 | minSdkVersion rootProject.ext.androidMinSdkVersion 10 | targetSdkVersion rootProject.ext.androidTargetSdkVersion 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | Map dependencies = rootProject.ext.dependencies; 24 | 25 | compile dependencies.appCompat 26 | compile dependencies.cardView 27 | compile dependencies.recyclerView 28 | compile dependencies.retrofit 29 | compile dependencies.retrofitConverterGson 30 | compile dependencies.retrofitAdapterRxJava 31 | compile dependencies.picasso 32 | compile dependencies.rxAndroid 33 | compile dependencies.circleImageView 34 | } 35 | -------------------------------------------------------------------------------- /kapp-mvp/src/main/java/com/taishi/kapp_mvp/ArchiApplication.kt: -------------------------------------------------------------------------------- 1 | package com.taishi.kapp_mvp 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | 6 | import rx.Scheduler 7 | import rx.schedulers.Schedulers 8 | 9 | class ArchiApplication : Application() { 10 | 11 | //For setting mocks during testing 12 | var githubService: GithubService? = null 13 | get() { 14 | if (field == null) { 15 | this.githubService = GithubService.Factory.create() 16 | } 17 | return field 18 | } 19 | private var defaultSubscribeScheduler: Scheduler? = null 20 | 21 | fun defaultSubscribeScheduler(): Scheduler { 22 | if (defaultSubscribeScheduler == null) { 23 | defaultSubscribeScheduler = Schedulers.io() 24 | } 25 | return defaultSubscribeScheduler!! 26 | } 27 | 28 | //User to change scheduler from tests 29 | fun setDefaultSubscribeScheduler(scheduler: Scheduler) { 30 | this.defaultSubscribeScheduler = scheduler 31 | } 32 | 33 | companion object { 34 | 35 | operator fun get(context: Context): ArchiApplication { 36 | return context.applicationContext as ArchiApplication 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 14 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /kapp-mvp/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 15 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /kapp-mvvm/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 15 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /kapp/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 15 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /app-mvp/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion rootProject.ext.androidCompileSdkVersion 5 | buildToolsVersion rootProject.ext.androidBuildToolsVersion 6 | 7 | defaultConfig { 8 | applicationId "uk.ivanc.archimvp" 9 | minSdkVersion rootProject.ext.androidMinSdkVersion 10 | targetSdkVersion rootProject.ext.androidTargetSdkVersion 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | Map dependencies = rootProject.ext.dependencies; 24 | 25 | compile dependencies.appCompat 26 | compile dependencies.cardView 27 | compile dependencies.recyclerView 28 | compile dependencies.retrofit 29 | compile dependencies.retrofitConverterGson 30 | compile dependencies.retrofitAdapterRxJava 31 | compile dependencies.picasso 32 | compile dependencies.rxAndroid 33 | compile dependencies.circleImageView 34 | 35 | testCompile dependencies.jUnit 36 | testCompile dependencies.mockito 37 | testCompile dependencies.robolectric 38 | } -------------------------------------------------------------------------------- /app-mvp/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 14 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /kapp/src/main/java/com/taishi/kapp/ArchiApplication.kt: -------------------------------------------------------------------------------- 1 | package com.taishi.kapp 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | 6 | import com.taishi.kapp.model.GithubService 7 | 8 | import rx.Scheduler 9 | import rx.schedulers.Schedulers 10 | 11 | 12 | class ArchiApplication : Application() { 13 | 14 | //For setting mocks during testing 15 | var githubService: GithubService? = null 16 | get() { 17 | if (field == null) { 18 | this.githubService = GithubService.Factory.create() 19 | } 20 | return field 21 | } 22 | private var defaultSubscribeScheduler: Scheduler? = null 23 | 24 | fun defaultSubscribeScheduler(): Scheduler { 25 | if (defaultSubscribeScheduler == null) { 26 | defaultSubscribeScheduler = Schedulers.io() 27 | } 28 | return defaultSubscribeScheduler!! 29 | } 30 | 31 | //User to change scheduler from tests 32 | fun setDefaultSubscribeScheduler(scheduler: Scheduler) { 33 | this.defaultSubscribeScheduler = scheduler 34 | } 35 | 36 | companion object { 37 | 38 | operator fun get(context: Context): ArchiApplication { 39 | return context.applicationContext as ArchiApplication 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app-mvvm/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 14 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /kapp-mvvm/src/main/java/com/taishi/kapp_mvvm/ArchiApplication.kt: -------------------------------------------------------------------------------- 1 | package com.taishi.kapp_mvvm 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | 6 | import com.taishi.kapp_mvvm.model.GithubService 7 | 8 | import rx.Scheduler 9 | import rx.schedulers.Schedulers 10 | 11 | 12 | class ArchiApplication : Application() { 13 | 14 | //For setting mocks during testing 15 | var githubService: GithubService? = null 16 | get() { 17 | if (field == null) { 18 | this.githubService = GithubService.Factory.create() 19 | } 20 | return field 21 | } 22 | private var defaultSubscribeScheduler: Scheduler? = null 23 | 24 | fun defaultSubscribeScheduler(): Scheduler { 25 | if (defaultSubscribeScheduler == null) { 26 | defaultSubscribeScheduler = Schedulers.io() 27 | } 28 | return defaultSubscribeScheduler!! 29 | } 30 | 31 | //User to change scheduler from tests 32 | fun setDefaultSubscribeScheduler(scheduler: Scheduler) { 33 | this.defaultSubscribeScheduler = scheduler 34 | } 35 | 36 | companion object { 37 | 38 | operator fun get(context: Context): ArchiApplication { 39 | return context.applicationContext as ArchiApplication 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app-mvvm/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion rootProject.ext.androidCompileSdkVersion 5 | buildToolsVersion rootProject.ext.androidBuildToolsVersion 6 | 7 | defaultConfig { 8 | applicationId "uk.ivanc.archimvvm" 9 | minSdkVersion rootProject.ext.androidMinSdkVersion 10 | targetSdkVersion rootProject.ext.androidTargetSdkVersion 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | 15 | dataBinding { 16 | enabled = true 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | } 26 | 27 | dependencies { 28 | Map dependencies = rootProject.ext.dependencies; 29 | 30 | compile dependencies.appCompat 31 | compile dependencies.cardView 32 | compile dependencies.recyclerView 33 | compile dependencies.retrofit 34 | compile dependencies.retrofitConverterGson 35 | compile dependencies.retrofitAdapterRxJava 36 | compile dependencies.picasso 37 | compile dependencies.rxAndroid 38 | compile dependencies.circleImageView 39 | 40 | testCompile dependencies.jUnit 41 | testCompile dependencies.mockito 42 | testCompile dependencies.robolectric 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/uk/ivanc/archi/ArchiApplication.java: -------------------------------------------------------------------------------- 1 | package uk.ivanc.archi; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | import rx.Scheduler; 7 | import rx.schedulers.Schedulers; 8 | import uk.ivanc.archi.model.GithubService; 9 | 10 | public class ArchiApplication extends Application { 11 | 12 | private GithubService githubService; 13 | private Scheduler defaultSubscribeScheduler; 14 | 15 | public static ArchiApplication get(Context context) { 16 | return (ArchiApplication) context.getApplicationContext(); 17 | } 18 | 19 | public GithubService getGithubService() { 20 | if (githubService == null) { 21 | githubService = GithubService.Factory.create(); 22 | } 23 | return githubService; 24 | } 25 | 26 | //For setting mocks during testing 27 | public void setGithubService(GithubService githubService) { 28 | this.githubService = githubService; 29 | } 30 | 31 | public Scheduler defaultSubscribeScheduler() { 32 | if (defaultSubscribeScheduler == null) { 33 | defaultSubscribeScheduler = Schedulers.io(); 34 | } 35 | return defaultSubscribeScheduler; 36 | } 37 | 38 | //User to change scheduler from tests 39 | public void setDefaultSubscribeScheduler(Scheduler scheduler) { 40 | this.defaultSubscribeScheduler = scheduler; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app-mvp/src/main/java/uk/ivanc/archimvp/ArchiApplication.java: -------------------------------------------------------------------------------- 1 | package uk.ivanc.archimvp; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | import rx.Scheduler; 7 | import rx.schedulers.Schedulers; 8 | import uk.ivanc.archimvp.model.GithubService; 9 | 10 | public class ArchiApplication extends Application { 11 | 12 | private GithubService githubService; 13 | private Scheduler defaultSubscribeScheduler; 14 | 15 | public static ArchiApplication get(Context context) { 16 | return (ArchiApplication) context.getApplicationContext(); 17 | } 18 | 19 | public GithubService getGithubService() { 20 | if (githubService == null) { 21 | githubService = GithubService.Factory.create(); 22 | } 23 | return githubService; 24 | } 25 | 26 | //For setting mocks during testing 27 | public void setGithubService(GithubService githubService) { 28 | this.githubService = githubService; 29 | } 30 | 31 | public Scheduler defaultSubscribeScheduler() { 32 | if (defaultSubscribeScheduler == null) { 33 | defaultSubscribeScheduler = Schedulers.io(); 34 | } 35 | return defaultSubscribeScheduler; 36 | } 37 | 38 | //User to change scheduler from tests 39 | public void setDefaultSubscribeScheduler(Scheduler scheduler) { 40 | this.defaultSubscribeScheduler = scheduler; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /kapp/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | compileSdkVersion rootProject.ext.androidCompileSdkVersion 7 | buildToolsVersion rootProject.ext.androidBuildToolsVersion 8 | 9 | defaultConfig { 10 | applicationId "com.taishi.kapp" 11 | minSdkVersion rootProject.ext.androidMinSdkVersion 12 | targetSdkVersion rootProject.ext.androidTargetSdkVersion 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 17 | 18 | } 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | } 26 | 27 | dependencies { 28 | Map dependencies = rootProject.ext.dependencies; 29 | 30 | compile dependencies.appCompat 31 | compile dependencies.cardView 32 | compile dependencies.recyclerView 33 | compile dependencies.retrofit 34 | compile dependencies.retrofitConverterGson 35 | compile dependencies.retrofitAdapterRxJava 36 | compile dependencies.picasso 37 | compile dependencies.rxAndroid 38 | compile dependencies.circleImageView 39 | compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 40 | } 41 | -------------------------------------------------------------------------------- /app-mvvm/src/main/java/uk/ivanc/archimvvm/ArchiApplication.java: -------------------------------------------------------------------------------- 1 | package uk.ivanc.archimvvm; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | import rx.Scheduler; 7 | import rx.schedulers.Schedulers; 8 | import uk.ivanc.archimvvm.model.GithubService; 9 | 10 | public class ArchiApplication extends Application { 11 | 12 | private GithubService githubService; 13 | private Scheduler defaultSubscribeScheduler; 14 | 15 | public static ArchiApplication get(Context context) { 16 | return (ArchiApplication) context.getApplicationContext(); 17 | } 18 | 19 | public GithubService getGithubService() { 20 | if (githubService == null) { 21 | githubService = GithubService.Factory.create(); 22 | } 23 | return githubService; 24 | } 25 | 26 | //For setting mocks during testing 27 | public void setGithubService(GithubService githubService) { 28 | this.githubService = githubService; 29 | } 30 | 31 | public Scheduler defaultSubscribeScheduler() { 32 | if (defaultSubscribeScheduler == null) { 33 | defaultSubscribeScheduler = Schedulers.io(); 34 | } 35 | return defaultSubscribeScheduler; 36 | } 37 | 38 | //User to change scheduler from tests 39 | public void setDefaultSubscribeScheduler(Scheduler scheduler) { 40 | this.defaultSubscribeScheduler = scheduler; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /kapp-mvp/src/main/java/com/taishi/kapp_mvp/presenter/RepositoryPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.taishi.kapp_mvp.presenter 2 | 3 | import android.util.Log 4 | import com.taishi.kapp_mvp.ArchiApplication 5 | import com.taishi.kapp_mvp.view.RepositoryMvpView 6 | import rx.Subscription 7 | import rx.android.schedulers.AndroidSchedulers 8 | 9 | 10 | class RepositoryPresenter : Presenter { 11 | 12 | private var repositoryMvpView: RepositoryMvpView? = null 13 | private var subscription: Subscription? = null 14 | 15 | override fun attachView(view: RepositoryMvpView) { 16 | this.repositoryMvpView = view 17 | } 18 | 19 | override fun detachView() { 20 | this.repositoryMvpView = null 21 | if (subscription != null) subscription!!.unsubscribe() 22 | } 23 | 24 | fun loadOwner(userUrl: String) { 25 | val application = ArchiApplication[repositoryMvpView!!.getContext()] 26 | val githubService = application.githubService 27 | subscription = githubService!!.userFromUrl(userUrl) 28 | .observeOn(AndroidSchedulers.mainThread()) 29 | .subscribeOn(application.defaultSubscribeScheduler()) 30 | .subscribe { user -> 31 | Log.i(TAG, "Full user data loaded " + user) 32 | repositoryMvpView!!.showOwner(user) 33 | } 34 | } 35 | 36 | companion object { 37 | 38 | private val TAG = "RepositoryPresenter" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /kapp-mvp/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | compileSdkVersion rootProject.ext.androidCompileSdkVersion 7 | buildToolsVersion rootProject.ext.androidBuildToolsVersion 8 | 9 | defaultConfig { 10 | applicationId "com.taishi.karchimvp" 11 | minSdkVersion rootProject.ext.androidMinSdkVersion 12 | targetSdkVersion rootProject.ext.androidTargetSdkVersion 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 17 | 18 | } 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | } 26 | 27 | dependencies { 28 | Map dependencies = rootProject.ext.dependencies; 29 | 30 | compile dependencies.appCompat 31 | compile dependencies.cardView 32 | compile dependencies.recyclerView 33 | compile dependencies.retrofit 34 | compile dependencies.retrofitConverterGson 35 | compile dependencies.retrofitAdapterRxJava 36 | compile dependencies.picasso 37 | compile dependencies.rxAndroid 38 | compile dependencies.circleImageView 39 | compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 40 | 41 | testCompile dependencies.jUnit 42 | testCompile dependencies.mockito 43 | testCompile dependencies.robolectric 44 | } 45 | -------------------------------------------------------------------------------- /kapp-mvvm/src/main/java/com/taishi/kapp_mvvm/viewmodel/ItemRepoViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.taishi.kapp_mvvm.viewmodel 2 | 3 | import android.content.Context 4 | import android.databinding.BaseObservable 5 | import android.view.View 6 | 7 | import com.taishi.kapp_mvvm.R 8 | import com.taishi.kapp_mvvm.model.Repository 9 | import com.taishi.kapp_mvvm.view.RepositoryActivity 10 | 11 | 12 | /** 13 | * View model for each item in the repositories RecyclerView 14 | */ 15 | class ItemRepoViewModel(private val context: Context, private var repository: Repository?) : BaseObservable(), ViewModel { 16 | 17 | val name: String 18 | get() = repository!!.name!! 19 | 20 | val description: String 21 | get() = repository!!.description!! 22 | 23 | val stars: String 24 | get() = context.getString(R.string.text_stars, repository!!.stars) 25 | 26 | val watchers: String 27 | get() = context.getString(R.string.text_watchers, repository!!.watchers) 28 | 29 | val forks: String 30 | get() = context.getString(R.string.text_forks, repository!!.forks) 31 | 32 | fun onItemClick(view: View) { 33 | context.startActivity(RepositoryActivity.newIntent(context, repository!!)) 34 | } 35 | 36 | // Allows recycling ItemRepoViewModels within the recyclerview adapter 37 | fun setRepository(repository: Repository) { 38 | this.repository = repository 39 | notifyChange() 40 | } 41 | 42 | override fun destroy() { 43 | //In this case destroy doesn't need to do anything because there is not async calls 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /app-mvp/src/test/java/uk/ivanc/archimvp/util/MockModelFabric.java: -------------------------------------------------------------------------------- 1 | package uk.ivanc.archimvp.util; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Random; 6 | 7 | import uk.ivanc.archimvp.model.Repository; 8 | import uk.ivanc.archimvp.model.User; 9 | 10 | public class MockModelFabric { 11 | 12 | public static List newListOfRepositories(int numRepos) { 13 | List repositories = new ArrayList<>(numRepos); 14 | for (int i = 0; i < numRepos; i++) { 15 | repositories.add(newRepository("Repo " + i)); 16 | } 17 | return repositories; 18 | } 19 | 20 | public static Repository newRepository(String name) { 21 | Random random = new Random(); 22 | Repository repository = new Repository(); 23 | repository.name = name; 24 | repository.id = random.nextInt(10000); 25 | repository.description = "Description for " + name; 26 | repository.watchers = random.nextInt(100); 27 | repository.forks = random.nextInt(100); 28 | repository.stars = random.nextInt(100); 29 | repository.owner = newUser("User-" + name); 30 | return repository; 31 | } 32 | 33 | public static User newUser(String name) { 34 | Random random = new Random(); 35 | User user = new User(); 36 | user.id = random.nextInt(10000); 37 | user.name = name; 38 | user.email = name + "@email.com"; 39 | user.location = "Location of " + name; 40 | user.url = "http://user.com/" + name; 41 | user.avatarUrl = "http://user.com/image/" + name; 42 | return user; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app-mvvm/src/test/java/uk/ivanc/archimvvm/util/MockModelFabric.java: -------------------------------------------------------------------------------- 1 | package uk.ivanc.archimvvm.util; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Random; 6 | 7 | import uk.ivanc.archimvvm.model.Repository; 8 | import uk.ivanc.archimvvm.model.User; 9 | 10 | public class MockModelFabric { 11 | 12 | public static List newListOfRepositories(int numRepos) { 13 | List repositories = new ArrayList<>(numRepos); 14 | for (int i = 0; i < numRepos; i++) { 15 | repositories.add(newRepository("Repo " + i)); 16 | } 17 | return repositories; 18 | } 19 | 20 | public static Repository newRepository(String name) { 21 | Random random = new Random(); 22 | Repository repository = new Repository(); 23 | repository.name = name; 24 | repository.id = random.nextInt(10000); 25 | repository.description = "Description for " + name; 26 | repository.watchers = random.nextInt(100); 27 | repository.forks = random.nextInt(100); 28 | repository.stars = random.nextInt(100); 29 | repository.owner = newUser("User-" + name); 30 | return repository; 31 | } 32 | 33 | public static User newUser(String name) { 34 | Random random = new Random(); 35 | User user = new User(); 36 | user.id = random.nextInt(10000); 37 | user.name = name; 38 | user.email = name + "@email.com"; 39 | user.location = "Location of " + name; 40 | user.url = "http://user.com/" + name; 41 | user.avatarUrl = "http://user.com/image/" + name; 42 | return user; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /kapp-mvvm/src/test/java/com/taishi/kapp_mvvm/MockModelFabric.java: -------------------------------------------------------------------------------- 1 | package com.taishi.kapp_mvvm; 2 | 3 | import com.taishi.kapp_mvvm.model.Repository; 4 | import com.taishi.kapp_mvvm.model.User; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.Random; 9 | 10 | 11 | 12 | public class MockModelFabric { 13 | 14 | public static List newListOfRepositories(int numRepos) { 15 | List repositories = new ArrayList<>(numRepos); 16 | for (int i = 0; i < numRepos; i++) { 17 | repositories.add(newRepository("Repo " + i)); 18 | } 19 | return repositories; 20 | } 21 | 22 | public static Repository newRepository(String name) { 23 | Random random = new Random(); 24 | Repository repository = new Repository(); 25 | repository.name = name; 26 | repository.id = random.nextInt(10000); 27 | repository.description = "Description for " + name; 28 | repository.watchers = random.nextInt(100); 29 | repository.forks = random.nextInt(100); 30 | repository.stars = random.nextInt(100); 31 | repository.owner = newUser("User-" + name); 32 | return repository; 33 | } 34 | 35 | public static User newUser(String name) { 36 | Random random = new Random(); 37 | User user = new User(); 38 | user.id = random.nextInt(10000); 39 | user.name = name; 40 | user.email = name + "@email.com"; 41 | user.location = "Location of " + name; 42 | user.url = "http://user.com/" + name; 43 | user.avatarUrl = "http://user.com/image/" + name; 44 | return user; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /kapp-mvp/src/test/java/com/taishi/kapp_mvp/util/MockModelFabric.java: -------------------------------------------------------------------------------- 1 | package com.taishi.kapp_mvp.util; 2 | 3 | import com.taishi.kapp_mvp.Repository; 4 | import com.taishi.kapp_mvp.User; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.Random; 9 | 10 | 11 | public class MockModelFabric { 12 | 13 | public static List newListOfRepositories(int numRepos) { 14 | List repositories = new ArrayList<>(numRepos); 15 | for (int i = 0; i < numRepos; i++) { 16 | repositories.add(newRepository("Repo " + i)); 17 | } 18 | return repositories; 19 | } 20 | 21 | public static Repository newRepository(String name) { 22 | Random random = new Random(); 23 | Repository repository = new Repository(); 24 | repository.setName(name); 25 | repository.setId(random.nextInt(10000)); 26 | repository.setDescription("Description for " + name); 27 | repository.setWatchers(random.nextInt(100)); 28 | repository.setForks(random.nextInt(100)); 29 | repository.setStars(random.nextInt(100)); 30 | repository.setOwner(newUser("User-" + name)); 31 | return repository; 32 | } 33 | 34 | public static User newUser(String name) { 35 | Random random = new Random(); 36 | User user = new User(); 37 | user.setId(random.nextInt(10000)); 38 | user.setName(name); 39 | user.setEmail(name + "@email.com"); 40 | user.setLocation("Location of " + name); 41 | user.setUrl("http://user.com/" + name); 42 | user.setAvatarUrl("http://user.com/image/" + name); 43 | return user; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /kapp-mvvm/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply plugin: "kotlin-kapt" 5 | 6 | android { 7 | compileSdkVersion rootProject.ext.androidCompileSdkVersion 8 | buildToolsVersion rootProject.ext.androidBuildToolsVersion 9 | 10 | defaultConfig { 11 | applicationId "com.taishi.karchimvvm" 12 | minSdkVersion rootProject.ext.androidMinSdkVersion 13 | targetSdkVersion rootProject.ext.androidTargetSdkVersion 14 | versionCode 1 15 | versionName "1.0" 16 | 17 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 18 | 19 | } 20 | 21 | dataBinding { 22 | enabled = true 23 | } 24 | 25 | buildTypes { 26 | release { 27 | minifyEnabled false 28 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 29 | } 30 | } 31 | } 32 | 33 | dependencies { 34 | Map dependencies = rootProject.ext.dependencies; 35 | 36 | compile dependencies.appCompat 37 | compile dependencies.cardView 38 | compile dependencies.recyclerView 39 | compile dependencies.retrofit 40 | compile dependencies.retrofitConverterGson 41 | compile dependencies.retrofitAdapterRxJava 42 | compile dependencies.picasso 43 | compile dependencies.rxAndroid 44 | compile dependencies.circleImageView 45 | 46 | testCompile dependencies.jUnit 47 | testCompile dependencies.mockito 48 | testCompile dependencies.robolectric 49 | compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 50 | kapt "com.android.databinding:compiler:$plugin_version" 51 | } 52 | 53 | -------------------------------------------------------------------------------- /app-mvp/src/main/java/uk/ivanc/archimvp/presenter/RepositoryPresenter.java: -------------------------------------------------------------------------------- 1 | package uk.ivanc.archimvp.presenter; 2 | 3 | import android.util.Log; 4 | 5 | import rx.Subscription; 6 | import rx.android.schedulers.AndroidSchedulers; 7 | import rx.functions.Action1; 8 | import uk.ivanc.archimvp.ArchiApplication; 9 | import uk.ivanc.archimvp.model.GithubService; 10 | import uk.ivanc.archimvp.model.User; 11 | import uk.ivanc.archimvp.view.RepositoryMvpView; 12 | 13 | public class RepositoryPresenter implements Presenter { 14 | 15 | private static final String TAG = "RepositoryPresenter"; 16 | 17 | private RepositoryMvpView repositoryMvpView; 18 | private Subscription subscription; 19 | 20 | @Override 21 | public void attachView(RepositoryMvpView view) { 22 | this.repositoryMvpView = view; 23 | } 24 | 25 | @Override 26 | public void detachView() { 27 | this.repositoryMvpView = null; 28 | if (subscription != null) subscription.unsubscribe(); 29 | } 30 | 31 | public void loadOwner(String userUrl) { 32 | ArchiApplication application = ArchiApplication.get(repositoryMvpView.getContext()); 33 | GithubService githubService = application.getGithubService(); 34 | subscription = githubService.userFromUrl(userUrl) 35 | .observeOn(AndroidSchedulers.mainThread()) 36 | .subscribeOn(application.defaultSubscribeScheduler()) 37 | .subscribe(new Action1() { 38 | @Override 39 | public void call(User user) { 40 | Log.i(TAG, "Full user data loaded " + user); 41 | repositoryMvpView.showOwner(user); 42 | } 43 | }); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app-mvvm/src/main/java/uk/ivanc/archimvvm/viewmodel/ItemRepoViewModel.java: -------------------------------------------------------------------------------- 1 | package uk.ivanc.archimvvm.viewmodel; 2 | 3 | import android.content.Context; 4 | import android.databinding.BaseObservable; 5 | import android.view.View; 6 | 7 | import uk.ivanc.archimvvm.R; 8 | import uk.ivanc.archimvvm.model.Repository; 9 | import uk.ivanc.archimvvm.view.RepositoryActivity; 10 | 11 | /** 12 | * View model for each item in the repositories RecyclerView 13 | */ 14 | public class ItemRepoViewModel extends BaseObservable implements ViewModel { 15 | 16 | private Repository repository; 17 | private Context context; 18 | 19 | public ItemRepoViewModel(Context context, Repository repository) { 20 | this.repository = repository; 21 | this.context = context; 22 | } 23 | 24 | public String getName() { 25 | return repository.name; 26 | } 27 | 28 | public String getDescription() { 29 | return repository.description; 30 | } 31 | 32 | public String getStars() { 33 | return context.getString(R.string.text_stars, repository.stars); 34 | } 35 | 36 | public String getWatchers() { 37 | return context.getString(R.string.text_watchers, repository.watchers); 38 | } 39 | 40 | public String getForks() { 41 | return context.getString(R.string.text_forks, repository.forks); 42 | } 43 | 44 | public void onItemClick(View view) { 45 | context.startActivity(RepositoryActivity.newIntent(context, repository)); 46 | } 47 | 48 | // Allows recycling ItemRepoViewModels within the recyclerview adapter 49 | public void setRepository(Repository repository) { 50 | this.repository = repository; 51 | notifyChange(); 52 | } 53 | 54 | @Override 55 | public void destroy() { 56 | //In this case destroy doesn't need to do anything because there is not async calls 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /kapp-mvvm/src/main/java/com/taishi/kapp_mvvm/view/RepositoryActivity.kt: -------------------------------------------------------------------------------- 1 | package com.taishi.kapp_mvvm.view 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.databinding.DataBindingUtil 6 | import android.os.Bundle 7 | import android.support.v7.app.AppCompatActivity 8 | import com.taishi.kapp_mvvm.R 9 | import com.taishi.kapp_mvvm.databinding.RepositoryActivityBinding 10 | import com.taishi.kapp_mvvm.model.Repository 11 | import com.taishi.kapp_mvvm.viewmodel.RepositoryViewModel 12 | 13 | 14 | class RepositoryActivity : AppCompatActivity() { 15 | 16 | private var binding: RepositoryActivityBinding? = null 17 | private var repositoryViewModel: RepositoryViewModel? = null 18 | 19 | override fun onCreate(savedInstanceState: Bundle?) { 20 | super.onCreate(savedInstanceState) 21 | binding = DataBindingUtil.setContentView(this, R.layout.repository_activity) 22 | setSupportActionBar(binding!!.toolbar) 23 | val actionBar = supportActionBar 24 | actionBar?.setDisplayHomeAsUpEnabled(true) 25 | 26 | val repository = intent.getParcelableExtra(EXTRA_REPOSITORY) 27 | repositoryViewModel = RepositoryViewModel(this, repository) 28 | binding!!.viewModel = repositoryViewModel 29 | 30 | //Currently there is no way of setting an activity title using data binding 31 | title = repository.name 32 | } 33 | 34 | override fun onDestroy() { 35 | super.onDestroy() 36 | repositoryViewModel!!.destroy() 37 | } 38 | 39 | companion object { 40 | 41 | private val EXTRA_REPOSITORY = "EXTRA_REPOSITORY" 42 | 43 | fun newIntent(context: Context, repository: Repository): Intent { 44 | val intent = Intent(context, RepositoryActivity::class.java) 45 | intent.putExtra(EXTRA_REPOSITORY, repository) 46 | return intent 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app-mvvm/src/main/java/uk/ivanc/archimvvm/view/RepositoryActivity.java: -------------------------------------------------------------------------------- 1 | package uk.ivanc.archimvvm.view; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.databinding.DataBindingUtil; 6 | import android.os.Bundle; 7 | import android.support.v7.app.ActionBar; 8 | import android.support.v7.app.AppCompatActivity; 9 | 10 | import uk.ivanc.archimvvm.R; 11 | import uk.ivanc.archimvvm.databinding.RepositoryActivityBinding; 12 | import uk.ivanc.archimvvm.model.Repository; 13 | import uk.ivanc.archimvvm.viewmodel.RepositoryViewModel; 14 | 15 | public class RepositoryActivity extends AppCompatActivity { 16 | 17 | private static final String EXTRA_REPOSITORY = "EXTRA_REPOSITORY"; 18 | 19 | private RepositoryActivityBinding binding; 20 | private RepositoryViewModel repositoryViewModel; 21 | 22 | public static Intent newIntent(Context context, Repository repository) { 23 | Intent intent = new Intent(context, RepositoryActivity.class); 24 | intent.putExtra(EXTRA_REPOSITORY, repository); 25 | return intent; 26 | } 27 | 28 | @Override 29 | protected void onCreate(Bundle savedInstanceState) { 30 | super.onCreate(savedInstanceState); 31 | binding = DataBindingUtil.setContentView(this, R.layout.repository_activity); 32 | setSupportActionBar(binding.toolbar); 33 | ActionBar actionBar = getSupportActionBar(); 34 | if (actionBar != null) { 35 | actionBar.setDisplayHomeAsUpEnabled(true); 36 | } 37 | 38 | Repository repository = getIntent().getParcelableExtra(EXTRA_REPOSITORY); 39 | repositoryViewModel = new RepositoryViewModel(this, repository); 40 | binding.setViewModel(repositoryViewModel); 41 | 42 | //Currently there is no way of setting an activity title using data binding 43 | setTitle(repository.name); 44 | } 45 | 46 | @Override 47 | protected void onDestroy() { 48 | super.onDestroy(); 49 | repositoryViewModel.destroy(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /kapp-mvvm/src/main/java/com/taishi/kapp_mvvm/RepositoryAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.taishi.kapp_mvvm 2 | 3 | import android.databinding.DataBindingUtil 4 | import android.support.v7.widget.RecyclerView 5 | import android.view.LayoutInflater 6 | import android.view.ViewGroup 7 | import com.taishi.kapp_mvvm.databinding.ItemRepoBinding 8 | import com.taishi.kapp_mvvm.model.Repository 9 | import com.taishi.kapp_mvvm.viewmodel.ItemRepoViewModel 10 | 11 | 12 | class RepositoryAdapter : RecyclerView.Adapter { 13 | 14 | private var repositories: List? = null 15 | 16 | constructor() { 17 | this.repositories = emptyList() 18 | } 19 | 20 | constructor(repositories: List) { 21 | this.repositories = repositories 22 | } 23 | 24 | fun setRepositories(repositories: List) { 25 | this.repositories = repositories 26 | } 27 | 28 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RepositoryViewHolder { 29 | val binding = DataBindingUtil.inflate( 30 | LayoutInflater.from(parent.context), 31 | R.layout.item_repo, 32 | parent, 33 | false) 34 | return RepositoryViewHolder(binding) 35 | } 36 | 37 | override fun onBindViewHolder(holder: RepositoryViewHolder, position: Int) { 38 | holder.bindRepository(repositories!![position]) 39 | } 40 | 41 | override fun getItemCount(): Int { 42 | return repositories!!.size 43 | } 44 | 45 | class RepositoryViewHolder(internal val binding: ItemRepoBinding) : RecyclerView.ViewHolder(binding.cardView) { 46 | 47 | internal fun bindRepository(repository: Repository) { 48 | if (binding.viewModel == null) { 49 | binding.viewModel = ItemRepoViewModel(itemView.context, repository) 50 | } else { 51 | binding.viewModel.setRepository(repository) 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /kapp-mvvm/src/main/java/com/taishi/kapp_mvvm/view/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.taishi.kapp_mvvm.view 2 | 3 | import android.content.Context 4 | import android.databinding.DataBindingUtil 5 | import android.os.Bundle 6 | import android.support.v7.app.AppCompatActivity 7 | import android.support.v7.widget.LinearLayoutManager 8 | import android.support.v7.widget.RecyclerView 9 | import android.view.inputmethod.InputMethodManager 10 | 11 | import com.taishi.kapp_mvvm.R 12 | import com.taishi.kapp_mvvm.RepositoryAdapter 13 | import com.taishi.kapp_mvvm.databinding.MainActivityBinding 14 | import com.taishi.kapp_mvvm.model.Repository 15 | import com.taishi.kapp_mvvm.viewmodel.MainViewModel 16 | 17 | 18 | class MainActivity : AppCompatActivity(), MainViewModel.DataListener { 19 | 20 | private var binding: MainActivityBinding? = null 21 | private var mainViewModel: MainViewModel? = null 22 | 23 | override fun onCreate(savedInstanceState: Bundle?) { 24 | super.onCreate(savedInstanceState) 25 | binding = DataBindingUtil.setContentView(this, R.layout.main_activity) 26 | mainViewModel = MainViewModel(this, this) 27 | binding!!.viewModel = mainViewModel 28 | setSupportActionBar(binding!!.toolbar) 29 | setupRecyclerView(binding!!.reposRecyclerView) 30 | } 31 | 32 | override fun onDestroy() { 33 | super.onDestroy() 34 | mainViewModel!!.destroy() 35 | } 36 | 37 | override fun onRepositoriesChanged(repositories: List) { 38 | val adapter = binding!!.reposRecyclerView.adapter as RepositoryAdapter 39 | adapter.setRepositories(repositories) 40 | adapter.notifyDataSetChanged() 41 | hideSoftKeyboard() 42 | } 43 | 44 | private fun setupRecyclerView(recyclerView: RecyclerView) { 45 | val adapter = RepositoryAdapter() 46 | recyclerView.adapter = adapter 47 | recyclerView.layoutManager = LinearLayoutManager(this) 48 | } 49 | 50 | private fun hideSoftKeyboard() { 51 | val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager 52 | imm.hideSoftInputFromWindow(binding!!.editTextUsername.windowToken, 0) 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /kapp-mvp/src/test/java/com/taishi/kapp_mvp/RepositoryPresenterTest.java: -------------------------------------------------------------------------------- 1 | package com.taishi.kapp_mvp; 2 | 3 | import com.taishi.kapp_mvp.presenter.RepositoryPresenter; 4 | import com.taishi.kapp_mvp.util.MockModelFabric; 5 | import com.taishi.kapp_mvp.view.RepositoryMvpView; 6 | 7 | import org.junit.After; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.robolectric.RobolectricGradleTestRunner; 12 | import org.robolectric.RuntimeEnvironment; 13 | import org.robolectric.annotation.Config; 14 | 15 | import rx.Observable; 16 | import rx.schedulers.Schedulers; 17 | 18 | import static org.mockito.Mockito.mock; 19 | import static org.mockito.Mockito.verify; 20 | import static org.mockito.Mockito.when; 21 | 22 | @RunWith(RobolectricGradleTestRunner.class) 23 | @Config(constants = BuildConfig.class, sdk = 21) 24 | public class RepositoryPresenterTest { 25 | RepositoryPresenter repositoryPresenter; 26 | RepositoryMvpView repositoryMvpView; 27 | GithubService githubService; 28 | 29 | @Before 30 | public void setUp() { 31 | ArchiApplication application = (ArchiApplication) RuntimeEnvironment.application; 32 | githubService = mock(GithubService.class); 33 | // Mock the retrofit service so we don't call the API directly 34 | application.setGithubService(githubService); 35 | // Change the default subscribe schedulers so all observables 36 | // will now run on the same thread 37 | application.setDefaultSubscribeScheduler(Schedulers.immediate()); 38 | repositoryPresenter = new RepositoryPresenter(); 39 | repositoryMvpView = mock(RepositoryMvpView.class); 40 | when(repositoryMvpView.getContext()).thenReturn(application); 41 | repositoryPresenter.attachView(repositoryMvpView); 42 | } 43 | 44 | @After 45 | public void tearDown() { 46 | repositoryPresenter.detachView(); 47 | } 48 | 49 | @Test 50 | public void loadOwnerCallsShowOwner() { 51 | User owner = MockModelFabric.newUser("ivan"); 52 | String userUrl = "http://user.com/more"; 53 | when(githubService.userFromUrl(userUrl)) 54 | .thenReturn(Observable.just(owner)); 55 | 56 | repositoryPresenter.loadOwner(userUrl); 57 | verify(repositoryMvpView).showOwner(owner); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app-mvvm/src/main/java/uk/ivanc/archimvvm/view/MainActivity.java: -------------------------------------------------------------------------------- 1 | package uk.ivanc.archimvvm.view; 2 | 3 | import android.content.Context; 4 | import android.databinding.DataBindingUtil; 5 | import android.os.Bundle; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.support.v7.widget.LinearLayoutManager; 8 | import android.support.v7.widget.RecyclerView; 9 | import android.view.inputmethod.InputMethodManager; 10 | 11 | import java.util.List; 12 | 13 | import uk.ivanc.archimvvm.R; 14 | import uk.ivanc.archimvvm.RepositoryAdapter; 15 | import uk.ivanc.archimvvm.databinding.MainActivityBinding; 16 | import uk.ivanc.archimvvm.model.Repository; 17 | import uk.ivanc.archimvvm.viewmodel.MainViewModel; 18 | 19 | public class MainActivity extends AppCompatActivity implements MainViewModel.DataListener { 20 | 21 | private MainActivityBinding binding; 22 | private MainViewModel mainViewModel; 23 | 24 | @Override 25 | protected void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | binding = DataBindingUtil.setContentView(this, R.layout.main_activity); 28 | mainViewModel = new MainViewModel(this, this); 29 | binding.setViewModel(mainViewModel); 30 | setSupportActionBar(binding.toolbar); 31 | setupRecyclerView(binding.reposRecyclerView); 32 | } 33 | 34 | @Override 35 | protected void onDestroy() { 36 | super.onDestroy(); 37 | mainViewModel.destroy(); 38 | } 39 | 40 | @Override 41 | public void onRepositoriesChanged(List repositories) { 42 | RepositoryAdapter adapter = 43 | (RepositoryAdapter) binding.reposRecyclerView.getAdapter(); 44 | adapter.setRepositories(repositories); 45 | adapter.notifyDataSetChanged(); 46 | hideSoftKeyboard(); 47 | } 48 | 49 | private void setupRecyclerView(RecyclerView recyclerView) { 50 | RepositoryAdapter adapter = new RepositoryAdapter(); 51 | recyclerView.setAdapter(adapter); 52 | recyclerView.setLayoutManager(new LinearLayoutManager(this)); 53 | } 54 | 55 | private void hideSoftKeyboard() { 56 | InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); 57 | imm.hideSoftInputFromWindow(binding.editTextUsername.getWindowToken(), 0); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /app-mvvm/src/main/java/uk/ivanc/archimvvm/RepositoryAdapter.java: -------------------------------------------------------------------------------- 1 | package uk.ivanc.archimvvm; 2 | 3 | import android.databinding.DataBindingUtil; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.LayoutInflater; 6 | import android.view.ViewGroup; 7 | 8 | import java.util.Collections; 9 | import java.util.List; 10 | 11 | import uk.ivanc.archimvvm.databinding.ItemRepoBinding; 12 | import uk.ivanc.archimvvm.model.Repository; 13 | import uk.ivanc.archimvvm.viewmodel.ItemRepoViewModel; 14 | 15 | public class RepositoryAdapter extends RecyclerView.Adapter { 16 | 17 | private List repositories; 18 | 19 | public RepositoryAdapter() { 20 | this.repositories = Collections.emptyList(); 21 | } 22 | 23 | public RepositoryAdapter(List repositories) { 24 | this.repositories = repositories; 25 | } 26 | 27 | public void setRepositories(List repositories) { 28 | this.repositories = repositories; 29 | } 30 | 31 | @Override 32 | public RepositoryViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 33 | ItemRepoBinding binding = DataBindingUtil.inflate( 34 | LayoutInflater.from(parent.getContext()), 35 | R.layout.item_repo, 36 | parent, 37 | false); 38 | return new RepositoryViewHolder(binding); 39 | } 40 | 41 | @Override 42 | public void onBindViewHolder(RepositoryViewHolder holder, int position) { 43 | holder.bindRepository(repositories.get(position)); 44 | } 45 | 46 | @Override 47 | public int getItemCount() { 48 | return repositories.size(); 49 | } 50 | 51 | public static class RepositoryViewHolder extends RecyclerView.ViewHolder { 52 | final ItemRepoBinding binding; 53 | 54 | public RepositoryViewHolder(ItemRepoBinding binding) { 55 | super(binding.cardView); 56 | this.binding = binding; 57 | } 58 | 59 | void bindRepository(Repository repository) { 60 | if (binding.getViewModel() == null) { 61 | binding.setViewModel(new ItemRepoViewModel(itemView.getContext(), repository)); 62 | } else { 63 | binding.getViewModel().setRepository(repository); 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app-mvp/src/test/java/uk/ivanc/archimvp/RepositoryPresenterTest.java: -------------------------------------------------------------------------------- 1 | package uk.ivanc.archimvp; 2 | 3 | import org.junit.After; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.robolectric.RobolectricGradleTestRunner; 8 | import org.robolectric.RuntimeEnvironment; 9 | import org.robolectric.annotation.Config; 10 | 11 | import rx.Observable; 12 | import rx.schedulers.Schedulers; 13 | import uk.ivanc.archimvp.model.GithubService; 14 | import uk.ivanc.archimvp.model.User; 15 | import uk.ivanc.archimvp.presenter.RepositoryPresenter; 16 | import uk.ivanc.archimvp.util.MockModelFabric; 17 | import uk.ivanc.archimvp.view.RepositoryMvpView; 18 | 19 | import static org.mockito.Mockito.mock; 20 | import static org.mockito.Mockito.verify; 21 | import static org.mockito.Mockito.when; 22 | 23 | @RunWith(RobolectricGradleTestRunner.class) 24 | @Config(constants = BuildConfig.class, sdk = 21) 25 | public class RepositoryPresenterTest { 26 | RepositoryPresenter repositoryPresenter; 27 | RepositoryMvpView repositoryMvpView; 28 | GithubService githubService; 29 | 30 | @Before 31 | public void setUp() { 32 | ArchiApplication application = (ArchiApplication) RuntimeEnvironment.application; 33 | githubService = mock(GithubService.class); 34 | // Mock the retrofit service so we don't call the API directly 35 | application.setGithubService(githubService); 36 | // Change the default subscribe schedulers so all observables 37 | // will now run on the same thread 38 | application.setDefaultSubscribeScheduler(Schedulers.immediate()); 39 | repositoryPresenter = new RepositoryPresenter(); 40 | repositoryMvpView = mock(RepositoryMvpView.class); 41 | when(repositoryMvpView.getContext()).thenReturn(application); 42 | repositoryPresenter.attachView(repositoryMvpView); 43 | } 44 | 45 | @After 46 | public void tearDown() { 47 | repositoryPresenter.detachView(); 48 | } 49 | 50 | @Test 51 | public void loadOwnerCallsShowOwner() { 52 | User owner = MockModelFabric.newUser("ivan"); 53 | String userUrl = "http://user.com/more"; 54 | when(githubService.userFromUrl(userUrl)) 55 | .thenReturn(Observable.just(owner)); 56 | 57 | repositoryPresenter.loadOwner(userUrl); 58 | verify(repositoryMvpView).showOwner(owner); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /kapp-mvp/src/main/java/com/taishi/kapp_mvp/presenter/MainPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.taishi.kapp_mvp.presenter 2 | 3 | import android.util.Log 4 | import com.taishi.kapp_mvp.ArchiApplication 5 | import com.taishi.kapp_mvp.R 6 | import com.taishi.kapp_mvp.Repository 7 | import com.taishi.kapp_mvp.view.MainMvpView 8 | import retrofit2.adapter.rxjava.HttpException 9 | import rx.Subscriber 10 | import rx.Subscription 11 | import rx.android.schedulers.AndroidSchedulers 12 | 13 | 14 | class MainPresenter : Presenter { 15 | 16 | private var mainMvpView: MainMvpView? = null 17 | private var subscription: Subscription? = null 18 | private var repositories: List? = null 19 | 20 | override fun attachView(view: MainMvpView) { 21 | this.mainMvpView = view 22 | } 23 | 24 | override fun detachView() { 25 | this.mainMvpView = null 26 | if (subscription != null) subscription!!.unsubscribe() 27 | } 28 | 29 | fun loadRepositories(usernameEntered: String) { 30 | val username = usernameEntered.trim { it <= ' ' } 31 | if (username.isEmpty()) return 32 | 33 | mainMvpView!!.showProgressIndicator() 34 | if (subscription != null) subscription!!.unsubscribe() 35 | val application = ArchiApplication[mainMvpView!!.getContext()] 36 | val githubService = application.githubService 37 | subscription = githubService!!.publicRepositories(username) 38 | .observeOn(AndroidSchedulers.mainThread()) 39 | .subscribeOn(application.defaultSubscribeScheduler()) 40 | .subscribe(object : Subscriber>() { 41 | override fun onCompleted() { 42 | Log.i(TAG, "Repos loaded " + repositories!!) 43 | if (!repositories!!.isEmpty()) { 44 | mainMvpView!!.showRepositories(repositories!!) 45 | } else { 46 | mainMvpView!!.showMessage(R.string.text_empty_repos) 47 | } 48 | } 49 | 50 | override fun onError(error: Throwable) { 51 | Log.e(TAG, "Error loading GitHub repos ", error) 52 | if (isHttp404(error)) { 53 | mainMvpView!!.showMessage(R.string.error_username_not_found) 54 | } else { 55 | mainMvpView!!.showMessage(R.string.error_loading_repos) 56 | } 57 | } 58 | 59 | override fun onNext(repositories: List) { 60 | this@MainPresenter.repositories = repositories 61 | } 62 | }) 63 | } 64 | 65 | companion object { 66 | 67 | var TAG = "MainPresenter" 68 | 69 | private fun isHttp404(error: Throwable): Boolean { 70 | return error is HttpException && error.code() == 404 71 | } 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /app-mvp/src/main/java/uk/ivanc/archimvp/presenter/MainPresenter.java: -------------------------------------------------------------------------------- 1 | package uk.ivanc.archimvp.presenter; 2 | 3 | import android.util.Log; 4 | 5 | import java.util.List; 6 | 7 | import retrofit2.adapter.rxjava.HttpException; 8 | import rx.Subscriber; 9 | import rx.Subscription; 10 | import rx.android.schedulers.AndroidSchedulers; 11 | import uk.ivanc.archimvp.ArchiApplication; 12 | import uk.ivanc.archimvp.R; 13 | import uk.ivanc.archimvp.model.GithubService; 14 | import uk.ivanc.archimvp.model.Repository; 15 | import uk.ivanc.archimvp.view.MainMvpView; 16 | 17 | public class MainPresenter implements Presenter { 18 | 19 | public static String TAG = "MainPresenter"; 20 | 21 | private MainMvpView mainMvpView; 22 | private Subscription subscription; 23 | private List repositories; 24 | 25 | @Override 26 | public void attachView(MainMvpView view) { 27 | this.mainMvpView = view; 28 | } 29 | 30 | @Override 31 | public void detachView() { 32 | this.mainMvpView = null; 33 | if (subscription != null) subscription.unsubscribe(); 34 | } 35 | 36 | public void loadRepositories(String usernameEntered) { 37 | String username = usernameEntered.trim(); 38 | if (username.isEmpty()) return; 39 | 40 | mainMvpView.showProgressIndicator(); 41 | if (subscription != null) subscription.unsubscribe(); 42 | ArchiApplication application = ArchiApplication.get(mainMvpView.getContext()); 43 | GithubService githubService = application.getGithubService(); 44 | subscription = githubService.publicRepositories(username) 45 | .observeOn(AndroidSchedulers.mainThread()) 46 | .subscribeOn(application.defaultSubscribeScheduler()) 47 | .subscribe(new Subscriber>() { 48 | @Override 49 | public void onCompleted() { 50 | Log.i(TAG, "Repos loaded " + repositories); 51 | if (!repositories.isEmpty()) { 52 | mainMvpView.showRepositories(repositories); 53 | } else { 54 | mainMvpView.showMessage(R.string.text_empty_repos); 55 | } 56 | } 57 | 58 | @Override 59 | public void onError(Throwable error) { 60 | Log.e(TAG, "Error loading GitHub repos ", error); 61 | if (isHttp404(error)) { 62 | mainMvpView.showMessage(R.string.error_username_not_found); 63 | } else { 64 | mainMvpView.showMessage(R.string.error_loading_repos); 65 | } 66 | } 67 | 68 | @Override 69 | public void onNext(List repositories) { 70 | MainPresenter.this.repositories = repositories; 71 | } 72 | }); 73 | } 74 | 75 | private static boolean isHttp404(Throwable error) { 76 | return error instanceof HttpException && ((HttpException) error).code() == 404; 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /kapp-mvp/src/main/java/com/taishi/kapp_mvp/RepositoryAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.taishi.kapp_mvp 2 | 3 | import android.support.v7.widget.RecyclerView 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.TextView 8 | 9 | class RepositoryAdapter : RecyclerView.Adapter { 10 | 11 | private var repositories: List? = null 12 | private var callback: Callback? = null 13 | 14 | constructor() { 15 | this.repositories = emptyList() 16 | } 17 | 18 | constructor(repositories: List) { 19 | this.repositories = repositories 20 | } 21 | 22 | fun setRepositories(repositories: List) { 23 | this.repositories = repositories 24 | } 25 | 26 | fun setCallback(callback: Callback) { 27 | this.callback = callback 28 | } 29 | 30 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RepositoryViewHolder { 31 | val itemView = LayoutInflater.from(parent.context) 32 | .inflate(R.layout.item_repo, parent, false) 33 | val viewHolder = RepositoryViewHolder(itemView) 34 | viewHolder.contentLayout.setOnClickListener { 35 | if (callback != null) { 36 | callback!!.onItemClick(viewHolder.repository!!) 37 | } 38 | } 39 | return viewHolder 40 | } 41 | 42 | override fun onBindViewHolder(holder: RepositoryViewHolder, position: Int) { 43 | val repository = repositories!![position] 44 | val context = holder.titleTextView.context 45 | holder.repository = repository 46 | holder.titleTextView.text = repository.name 47 | holder.descriptionTextView.text = repository.description 48 | holder.watchersTextView.text = context.resources.getString(R.string.text_watchers, repository.watchers) 49 | holder.starsTextView.text = context.resources.getString(R.string.text_stars, repository.stars) 50 | holder.forksTextView.text = context.resources.getString(R.string.text_forks, repository.forks) 51 | } 52 | 53 | override fun getItemCount(): Int { 54 | return repositories!!.size 55 | } 56 | 57 | class RepositoryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 58 | var contentLayout: View 59 | var titleTextView: TextView 60 | var descriptionTextView: TextView 61 | var watchersTextView: TextView 62 | var starsTextView: TextView 63 | var forksTextView: TextView 64 | var repository: Repository? = null 65 | 66 | init { 67 | contentLayout = itemView.findViewById(R.id.layout_content) 68 | titleTextView = itemView.findViewById(R.id.text_repo_title) as TextView 69 | descriptionTextView = itemView.findViewById(R.id.text_repo_description) as TextView 70 | watchersTextView = itemView.findViewById(R.id.text_watchers) as TextView 71 | starsTextView = itemView.findViewById(R.id.text_stars) as TextView 72 | forksTextView = itemView.findViewById(R.id.text_forks) as TextView 73 | } 74 | } 75 | 76 | interface Callback { 77 | fun onItemClick(repository: Repository) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /kapp/src/main/java/com/taishi/kapp/RepositoryAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.taishi.kapp 2 | 3 | import android.support.v7.widget.RecyclerView 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.TextView 8 | import com.taishi.kapp.model.Repository 9 | 10 | 11 | class RepositoryAdapter : RecyclerView.Adapter { 12 | 13 | private var repositories: List? = null 14 | private var callback: Callback? = null 15 | 16 | constructor() { 17 | this.repositories = emptyList() 18 | } 19 | 20 | constructor(repositories: List) { 21 | this.repositories = repositories 22 | } 23 | 24 | fun setRepositories(repositories: List) { 25 | this.repositories = repositories 26 | } 27 | 28 | fun setCallback(callback: Callback) { 29 | this.callback = callback 30 | } 31 | 32 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RepositoryViewHolder { 33 | val itemView = LayoutInflater.from(parent.context) 34 | .inflate(R.layout.item_repo, parent, false) 35 | val viewHolder = RepositoryViewHolder(itemView) 36 | viewHolder.contentLayout.setOnClickListener { 37 | if (callback != null) { 38 | callback!!.onItemClick(viewHolder.repository!!) 39 | } 40 | } 41 | return viewHolder 42 | } 43 | 44 | override fun onBindViewHolder(holder: RepositoryViewHolder, position: Int) { 45 | val repository = repositories!![position] 46 | val context = holder.titleTextView.context 47 | holder.repository = repository 48 | holder.titleTextView.text = repository.name 49 | holder.descriptionTextView.text = repository.description 50 | holder.watchersTextView.text = context.resources.getString(R.string.text_watchers, repository.watchers) 51 | holder.starsTextView.text = context.resources.getString(R.string.text_stars, repository.stars) 52 | holder.forksTextView.text = context.resources.getString(R.string.text_forks, repository.forks) 53 | } 54 | 55 | override fun getItemCount(): Int { 56 | return repositories!!.size 57 | } 58 | 59 | class RepositoryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 60 | var contentLayout: View 61 | var titleTextView: TextView 62 | var descriptionTextView: TextView 63 | var watchersTextView: TextView 64 | var starsTextView: TextView 65 | var forksTextView: TextView 66 | var repository: Repository? = null 67 | 68 | init { 69 | contentLayout = itemView.findViewById(R.id.layout_content) 70 | titleTextView = itemView.findViewById(R.id.text_repo_title) as TextView 71 | descriptionTextView = itemView.findViewById(R.id.text_repo_description) as TextView 72 | watchersTextView = itemView.findViewById(R.id.text_watchers) as TextView 73 | starsTextView = itemView.findViewById(R.id.text_stars) as TextView 74 | forksTextView = itemView.findViewById(R.id.text_forks) as TextView 75 | } 76 | } 77 | 78 | interface Callback { 79 | fun onItemClick(repository: Repository) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/java/uk/ivanc/archi/model/User.java: -------------------------------------------------------------------------------- 1 | package uk.ivanc.archi.model; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | import com.google.gson.annotations.SerializedName; 7 | 8 | public class User implements Parcelable { 9 | public long id; 10 | public String name; 11 | public String url; 12 | public String email; 13 | public String login; 14 | public String location; 15 | @SerializedName("avatar_url") 16 | public String avatarUrl; 17 | 18 | public User() { 19 | } 20 | 21 | public boolean hasEmail() { 22 | return email != null && !email.isEmpty(); 23 | } 24 | 25 | public boolean hasLocation() { 26 | return location != null && !location.isEmpty(); 27 | } 28 | 29 | @Override 30 | public int describeContents() { 31 | return 0; 32 | } 33 | 34 | @Override 35 | public void writeToParcel(Parcel dest, int flags) { 36 | dest.writeLong(this.id); 37 | dest.writeString(this.name); 38 | dest.writeString(this.url); 39 | dest.writeString(this.email); 40 | dest.writeString(this.login); 41 | dest.writeString(this.location); 42 | dest.writeString(this.avatarUrl); 43 | } 44 | 45 | protected User(Parcel in) { 46 | this.id = in.readLong(); 47 | this.name = in.readString(); 48 | this.url = in.readString(); 49 | this.email = in.readString(); 50 | this.login = in.readString(); 51 | this.location = in.readString(); 52 | this.avatarUrl = in.readString(); 53 | } 54 | 55 | public static final Creator CREATOR = new Creator() { 56 | public User createFromParcel(Parcel source) { 57 | return new User(source); 58 | } 59 | 60 | public User[] newArray(int size) { 61 | return new User[size]; 62 | } 63 | }; 64 | 65 | @Override 66 | public boolean equals(Object o) { 67 | if (this == o) return true; 68 | if (o == null || getClass() != o.getClass()) return false; 69 | 70 | User user = (User) o; 71 | 72 | if (id != user.id) return false; 73 | if (name != null ? !name.equals(user.name) : user.name != null) return false; 74 | if (url != null ? !url.equals(user.url) : user.url != null) return false; 75 | if (email != null ? !email.equals(user.email) : user.email != null) return false; 76 | if (login != null ? !login.equals(user.login) : user.login != null) return false; 77 | if (location != null ? !location.equals(user.location) : user.location != null) 78 | return false; 79 | return !(avatarUrl != null ? !avatarUrl.equals(user.avatarUrl) : user.avatarUrl != null); 80 | 81 | } 82 | 83 | @Override 84 | public int hashCode() { 85 | int result = (int) (id ^ (id >>> 32)); 86 | result = 31 * result + (name != null ? name.hashCode() : 0); 87 | result = 31 * result + (url != null ? url.hashCode() : 0); 88 | result = 31 * result + (email != null ? email.hashCode() : 0); 89 | result = 31 * result + (login != null ? login.hashCode() : 0); 90 | result = 31 * result + (location != null ? location.hashCode() : 0); 91 | result = 31 * result + (avatarUrl != null ? avatarUrl.hashCode() : 0); 92 | return result; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /app-mvp/src/main/java/uk/ivanc/archimvp/model/User.java: -------------------------------------------------------------------------------- 1 | package uk.ivanc.archimvp.model; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | import com.google.gson.annotations.SerializedName; 7 | 8 | public class User implements Parcelable { 9 | public long id; 10 | public String name; 11 | public String url; 12 | public String email; 13 | public String login; 14 | public String location; 15 | @SerializedName("avatar_url") 16 | public String avatarUrl; 17 | 18 | public User() { 19 | } 20 | 21 | public boolean hasEmail() { 22 | return email != null && !email.isEmpty(); 23 | } 24 | 25 | public boolean hasLocation() { 26 | return location != null && !location.isEmpty(); 27 | } 28 | 29 | @Override 30 | public int describeContents() { 31 | return 0; 32 | } 33 | 34 | @Override 35 | public void writeToParcel(Parcel dest, int flags) { 36 | dest.writeLong(this.id); 37 | dest.writeString(this.name); 38 | dest.writeString(this.url); 39 | dest.writeString(this.email); 40 | dest.writeString(this.login); 41 | dest.writeString(this.location); 42 | dest.writeString(this.avatarUrl); 43 | } 44 | 45 | protected User(Parcel in) { 46 | this.id = in.readLong(); 47 | this.name = in.readString(); 48 | this.url = in.readString(); 49 | this.email = in.readString(); 50 | this.login = in.readString(); 51 | this.location = in.readString(); 52 | this.avatarUrl = in.readString(); 53 | } 54 | 55 | public static final Creator CREATOR = new Creator() { 56 | public User createFromParcel(Parcel source) { 57 | return new User(source); 58 | } 59 | 60 | public User[] newArray(int size) { 61 | return new User[size]; 62 | } 63 | }; 64 | 65 | @Override 66 | public boolean equals(Object o) { 67 | if (this == o) return true; 68 | if (o == null || getClass() != o.getClass()) return false; 69 | 70 | User user = (User) o; 71 | 72 | if (id != user.id) return false; 73 | if (name != null ? !name.equals(user.name) : user.name != null) return false; 74 | if (url != null ? !url.equals(user.url) : user.url != null) return false; 75 | if (email != null ? !email.equals(user.email) : user.email != null) return false; 76 | if (login != null ? !login.equals(user.login) : user.login != null) return false; 77 | if (location != null ? !location.equals(user.location) : user.location != null) 78 | return false; 79 | return !(avatarUrl != null ? !avatarUrl.equals(user.avatarUrl) : user.avatarUrl != null); 80 | 81 | } 82 | 83 | @Override 84 | public int hashCode() { 85 | int result = (int) (id ^ (id >>> 32)); 86 | result = 31 * result + (name != null ? name.hashCode() : 0); 87 | result = 31 * result + (url != null ? url.hashCode() : 0); 88 | result = 31 * result + (email != null ? email.hashCode() : 0); 89 | result = 31 * result + (login != null ? login.hashCode() : 0); 90 | result = 31 * result + (location != null ? location.hashCode() : 0); 91 | result = 31 * result + (avatarUrl != null ? avatarUrl.hashCode() : 0); 92 | return result; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /app-mvvm/src/main/java/uk/ivanc/archimvvm/model/User.java: -------------------------------------------------------------------------------- 1 | package uk.ivanc.archimvvm.model; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | import com.google.gson.annotations.SerializedName; 7 | 8 | public class User implements Parcelable { 9 | public long id; 10 | public String name; 11 | public String url; 12 | public String email; 13 | public String login; 14 | public String location; 15 | @SerializedName("avatar_url") 16 | public String avatarUrl; 17 | 18 | public User() { 19 | } 20 | 21 | public boolean hasEmail() { 22 | return email != null && !email.isEmpty(); 23 | } 24 | 25 | public boolean hasLocation() { 26 | return location != null && !location.isEmpty(); 27 | } 28 | 29 | @Override 30 | public int describeContents() { 31 | return 0; 32 | } 33 | 34 | @Override 35 | public void writeToParcel(Parcel dest, int flags) { 36 | dest.writeLong(this.id); 37 | dest.writeString(this.name); 38 | dest.writeString(this.url); 39 | dest.writeString(this.email); 40 | dest.writeString(this.login); 41 | dest.writeString(this.location); 42 | dest.writeString(this.avatarUrl); 43 | } 44 | 45 | protected User(Parcel in) { 46 | this.id = in.readLong(); 47 | this.name = in.readString(); 48 | this.url = in.readString(); 49 | this.email = in.readString(); 50 | this.login = in.readString(); 51 | this.location = in.readString(); 52 | this.avatarUrl = in.readString(); 53 | } 54 | 55 | public static final Creator CREATOR = new Creator() { 56 | public User createFromParcel(Parcel source) { 57 | return new User(source); 58 | } 59 | 60 | public User[] newArray(int size) { 61 | return new User[size]; 62 | } 63 | }; 64 | 65 | @Override 66 | public boolean equals(Object o) { 67 | if (this == o) return true; 68 | if (o == null || getClass() != o.getClass()) return false; 69 | 70 | User user = (User) o; 71 | 72 | if (id != user.id) return false; 73 | if (name != null ? !name.equals(user.name) : user.name != null) return false; 74 | if (url != null ? !url.equals(user.url) : user.url != null) return false; 75 | if (email != null ? !email.equals(user.email) : user.email != null) return false; 76 | if (login != null ? !login.equals(user.login) : user.login != null) return false; 77 | if (location != null ? !location.equals(user.location) : user.location != null) 78 | return false; 79 | return !(avatarUrl != null ? !avatarUrl.equals(user.avatarUrl) : user.avatarUrl != null); 80 | 81 | } 82 | 83 | @Override 84 | public int hashCode() { 85 | int result = (int) (id ^ (id >>> 32)); 86 | result = 31 * result + (name != null ? name.hashCode() : 0); 87 | result = 31 * result + (url != null ? url.hashCode() : 0); 88 | result = 31 * result + (email != null ? email.hashCode() : 0); 89 | result = 31 * result + (login != null ? login.hashCode() : 0); 90 | result = 31 * result + (location != null ? location.hashCode() : 0); 91 | result = 31 * result + (avatarUrl != null ? avatarUrl.hashCode() : 0); 92 | return result; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /kapp-mvp/src/main/java/com/taishi/kapp_mvp/User.kt: -------------------------------------------------------------------------------- 1 | package com.taishi.kapp_mvp 2 | 3 | import android.os.Parcel 4 | import android.os.Parcelable 5 | 6 | import com.google.gson.annotations.SerializedName 7 | 8 | class User : Parcelable { 9 | var id: Long = 0 10 | var name: String? = null 11 | var url: String? = null 12 | var email: String? = null 13 | var login: String? = null 14 | var location: String? = null 15 | @SerializedName("avatar_url") 16 | var avatarUrl: String? = null 17 | 18 | constructor() {} 19 | 20 | fun hasEmail(): Boolean { 21 | return email != null && !email!!.isEmpty() 22 | } 23 | 24 | fun hasLocation(): Boolean { 25 | return location != null && !location!!.isEmpty() 26 | } 27 | 28 | override fun describeContents(): Int { 29 | return 0 30 | } 31 | 32 | override fun writeToParcel(dest: Parcel, flags: Int) { 33 | dest.writeLong(this.id) 34 | dest.writeString(this.name) 35 | dest.writeString(this.url) 36 | dest.writeString(this.email) 37 | dest.writeString(this.login) 38 | dest.writeString(this.location) 39 | dest.writeString(this.avatarUrl) 40 | } 41 | 42 | protected constructor(`in`: Parcel) { 43 | this.id = `in`.readLong() 44 | this.name = `in`.readString() 45 | this.url = `in`.readString() 46 | this.email = `in`.readString() 47 | this.login = `in`.readString() 48 | this.location = `in`.readString() 49 | this.avatarUrl = `in`.readString() 50 | } 51 | 52 | override fun equals(o: Any?): Boolean { 53 | if (this === o) return true 54 | if (o == null || javaClass != o.javaClass) return false 55 | 56 | val user = o as User? 57 | 58 | if (id != user!!.id) return false 59 | if (if (name != null) name != user.name else user.name != null) return false 60 | if (if (url != null) url != user.url else user.url != null) return false 61 | if (if (email != null) email != user.email else user.email != null) return false 62 | if (if (login != null) login != user.login else user.login != null) return false 63 | if (if (location != null) location != user.location else user.location != null) 64 | return false 65 | return !if (avatarUrl != null) avatarUrl != user.avatarUrl else user.avatarUrl != null 66 | 67 | } 68 | 69 | override fun hashCode(): Int { 70 | var result = (id xor id.ushr(32)).toInt() 71 | result = 31 * result + if (name != null) name!!.hashCode() else 0 72 | result = 31 * result + if (url != null) url!!.hashCode() else 0 73 | result = 31 * result + if (email != null) email!!.hashCode() else 0 74 | result = 31 * result + if (login != null) login!!.hashCode() else 0 75 | result = 31 * result + if (location != null) location!!.hashCode() else 0 76 | result = 31 * result + if (avatarUrl != null) avatarUrl!!.hashCode() else 0 77 | return result 78 | } 79 | 80 | companion object { 81 | 82 | val CREATOR: Parcelable.Creator = object : Parcelable.Creator { 83 | override fun createFromParcel(source: Parcel): User { 84 | return User(source) 85 | } 86 | 87 | override fun newArray(size: Int): Array { 88 | return arrayOfNulls(size) 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /kapp/src/main/java/com/taishi/kapp/model/User.kt: -------------------------------------------------------------------------------- 1 | package com.taishi.kapp.model 2 | 3 | import android.os.Parcel 4 | import android.os.Parcelable 5 | 6 | import com.google.gson.annotations.SerializedName 7 | 8 | class User : Parcelable { 9 | var id: Long = 0 10 | var name: String? = null 11 | var url: String? = null 12 | var email: String? = null 13 | var login: String? = null 14 | var location: String? = null 15 | @SerializedName("avatar_url") 16 | var avatarUrl: String? = null 17 | 18 | constructor() {} 19 | 20 | fun hasEmail(): Boolean { 21 | return email != null && !email!!.isEmpty() 22 | } 23 | 24 | fun hasLocation(): Boolean { 25 | return location != null && !location!!.isEmpty() 26 | } 27 | 28 | override fun describeContents(): Int { 29 | return 0 30 | } 31 | 32 | override fun writeToParcel(dest: Parcel, flags: Int) { 33 | dest.writeLong(this.id) 34 | dest.writeString(this.name) 35 | dest.writeString(this.url) 36 | dest.writeString(this.email) 37 | dest.writeString(this.login) 38 | dest.writeString(this.location) 39 | dest.writeString(this.avatarUrl) 40 | } 41 | 42 | protected constructor(`in`: Parcel) { 43 | this.id = `in`.readLong() 44 | this.name = `in`.readString() 45 | this.url = `in`.readString() 46 | this.email = `in`.readString() 47 | this.login = `in`.readString() 48 | this.location = `in`.readString() 49 | this.avatarUrl = `in`.readString() 50 | } 51 | 52 | override fun equals(o: Any?): Boolean { 53 | if (this === o) return true 54 | if (o == null || javaClass != o.javaClass) return false 55 | 56 | val user = o as User? 57 | 58 | if (id != user!!.id) return false 59 | if (if (name != null) name != user.name else user.name != null) return false 60 | if (if (url != null) url != user.url else user.url != null) return false 61 | if (if (email != null) email != user.email else user.email != null) return false 62 | if (if (login != null) login != user.login else user.login != null) return false 63 | if (if (location != null) location != user.location else user.location != null) 64 | return false 65 | return !if (avatarUrl != null) avatarUrl != user.avatarUrl else user.avatarUrl != null 66 | 67 | } 68 | 69 | override fun hashCode(): Int { 70 | var result = (id xor id.ushr(32)).toInt() 71 | result = 31 * result + if (name != null) name!!.hashCode() else 0 72 | result = 31 * result + if (url != null) url!!.hashCode() else 0 73 | result = 31 * result + if (email != null) email!!.hashCode() else 0 74 | result = 31 * result + if (login != null) login!!.hashCode() else 0 75 | result = 31 * result + if (location != null) location!!.hashCode() else 0 76 | result = 31 * result + if (avatarUrl != null) avatarUrl!!.hashCode() else 0 77 | return result 78 | } 79 | 80 | companion object { 81 | 82 | val CREATOR: Parcelable.Creator = object : Parcelable.Creator { 83 | override fun createFromParcel(source: Parcel): User { 84 | return User(source) 85 | } 86 | 87 | override fun newArray(size: Int): Array { 88 | return arrayOfNulls(size) 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /kapp-mvvm/src/main/java/com/taishi/kapp_mvvm/model/User.kt: -------------------------------------------------------------------------------- 1 | package com.taishi.kapp_mvvm.model 2 | 3 | import android.os.Parcel 4 | import android.os.Parcelable 5 | 6 | import com.google.gson.annotations.SerializedName 7 | 8 | class User : Parcelable { 9 | var id: Long = 0 10 | var name: String? = null 11 | var url: String? = null 12 | var email: String? = null 13 | var login: String? = null 14 | var location: String? = null 15 | @SerializedName("avatar_url") 16 | var avatarUrl: String? = null 17 | 18 | constructor() {} 19 | 20 | fun hasEmail(): Boolean { 21 | return email != null && !email!!.isEmpty() 22 | } 23 | 24 | fun hasLocation(): Boolean { 25 | return location != null && !location!!.isEmpty() 26 | } 27 | 28 | override fun describeContents(): Int { 29 | return 0 30 | } 31 | 32 | override fun writeToParcel(dest: Parcel, flags: Int) { 33 | dest.writeLong(this.id) 34 | dest.writeString(this.name) 35 | dest.writeString(this.url) 36 | dest.writeString(this.email) 37 | dest.writeString(this.login) 38 | dest.writeString(this.location) 39 | dest.writeString(this.avatarUrl) 40 | } 41 | 42 | protected constructor(`in`: Parcel) { 43 | this.id = `in`.readLong() 44 | this.name = `in`.readString() 45 | this.url = `in`.readString() 46 | this.email = `in`.readString() 47 | this.login = `in`.readString() 48 | this.location = `in`.readString() 49 | this.avatarUrl = `in`.readString() 50 | } 51 | 52 | override fun equals(o: Any?): Boolean { 53 | if (this === o) return true 54 | if (o == null || javaClass != o.javaClass) return false 55 | 56 | val user = o as User? 57 | 58 | if (id != user!!.id) return false 59 | if (if (name != null) name != user.name else user.name != null) return false 60 | if (if (url != null) url != user.url else user.url != null) return false 61 | if (if (email != null) email != user.email else user.email != null) return false 62 | if (if (login != null) login != user.login else user.login != null) return false 63 | if (if (location != null) location != user.location else user.location != null) 64 | return false 65 | return !if (avatarUrl != null) avatarUrl != user.avatarUrl else user.avatarUrl != null 66 | 67 | } 68 | 69 | override fun hashCode(): Int { 70 | var result = (id xor id.ushr(32)).toInt() 71 | result = 31 * result + if (name != null) name!!.hashCode() else 0 72 | result = 31 * result + if (url != null) url!!.hashCode() else 0 73 | result = 31 * result + if (email != null) email!!.hashCode() else 0 74 | result = 31 * result + if (login != null) login!!.hashCode() else 0 75 | result = 31 * result + if (location != null) location!!.hashCode() else 0 76 | result = 31 * result + if (avatarUrl != null) avatarUrl!!.hashCode() else 0 77 | return result 78 | } 79 | 80 | companion object { 81 | 82 | val CREATOR: Parcelable.Creator = object : Parcelable.Creator { 83 | override fun createFromParcel(source: Parcel): User { 84 | return User(source) 85 | } 86 | 87 | override fun newArray(size: Int): Array { 88 | return arrayOfNulls(size) 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app-mvp/src/main/res/layout/item_repo.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 20 | 21 | 32 | 33 | 44 | 45 | 49 | 50 | 54 | 55 | 63 | 64 | 72 | 73 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_repo.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 20 | 21 | 32 | 33 | 44 | 45 | 49 | 50 | 54 | 55 | 63 | 64 | 72 | 73 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /kapp/src/main/res/layout/item_repo.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 20 | 21 | 32 | 33 | 44 | 45 | 49 | 50 | 54 | 55 | 63 | 64 | 72 | 73 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /kapp-mvp/src/main/res/layout/item_repo.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 20 | 21 | 32 | 33 | 44 | 45 | 49 | 50 | 54 | 55 | 63 | 64 | 72 | 73 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /kapp/src/main/java/com/taishi/kapp/RepositoryActivity.kt: -------------------------------------------------------------------------------- 1 | package com.taishi.kapp 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import android.support.v7.app.AppCompatActivity 7 | import android.util.Log 8 | import android.view.View 9 | import com.squareup.picasso.Picasso 10 | import com.taishi.kapp.model.Repository 11 | import com.taishi.kapp.model.User 12 | import kotlinx.android.synthetic.main.activity_repository.* 13 | import rx.Subscription 14 | import rx.android.schedulers.AndroidSchedulers 15 | 16 | 17 | class RepositoryActivity : AppCompatActivity() { 18 | 19 | private var subscription: Subscription? = null 20 | 21 | override fun onCreate(savedInstanceState: Bundle?) { 22 | super.onCreate(savedInstanceState) 23 | setContentView(R.layout.activity_repository) 24 | setSupportActionBar(toolbar) 25 | val actionBar = supportActionBar 26 | actionBar?.setDisplayHomeAsUpEnabled(true) 27 | 28 | val repository = intent.getParcelableExtra(EXTRA_REPOSITORY) 29 | bindRepositoryData(repository) 30 | loadFullUser(repository.owner!!.url!!) 31 | } 32 | 33 | override fun onDestroy() { 34 | super.onDestroy() 35 | if (subscription != null) subscription!!.unsubscribe() 36 | } 37 | 38 | private fun bindRepositoryData(repository: Repository) { 39 | title = repository.name 40 | text_repo_description.text = repository.description 41 | text_homepage.text = repository.homepage 42 | text_homepage.visibility = if (repository.hasHomepage()) View.VISIBLE else View.GONE 43 | text_language.text = getString(R.string.text_language, repository.language) 44 | text_language.visibility = if (repository.hasLanguage()) View.VISIBLE else View.GONE 45 | text_fork.visibility = if (repository.isFork) View.VISIBLE else View.GONE 46 | //Preload image for user because we already have it before loading the full user 47 | Picasso.with(this) 48 | .load(repository.owner!!.avatarUrl) 49 | .placeholder(R.drawable.placeholder) 50 | .into(image_owner) 51 | } 52 | 53 | private fun bindOwnerData(owner: User) { 54 | text_owner_name.text = owner.name 55 | text_owner_email.text = owner.email 56 | text_owner_email.visibility = if (owner.hasEmail()) View.VISIBLE else View.GONE 57 | text_owner_location.text = owner.location 58 | text_owner_location.visibility = if (owner.hasLocation()) View.VISIBLE else View.GONE 59 | } 60 | 61 | 62 | private fun loadFullUser(url: String) { 63 | val application = ArchiApplication[this] 64 | val githubService = application.githubService 65 | subscription = githubService!!.userFromUrl(url) 66 | .observeOn(AndroidSchedulers.mainThread()) 67 | .subscribeOn(application.defaultSubscribeScheduler()) 68 | .subscribe { user -> 69 | Log.i(TAG, "Full user data loaded " + user) 70 | bindOwnerData(user) 71 | layout_owner.visibility = View.VISIBLE 72 | } 73 | } 74 | 75 | companion object { 76 | 77 | private val EXTRA_REPOSITORY = "EXTRA_REPOSITORY" 78 | private val TAG = "RepositoryActivity" 79 | 80 | fun newIntent(context: Context, repository: Repository): Intent { 81 | val intent = Intent(context, RepositoryActivity::class.java) 82 | intent.putExtra(EXTRA_REPOSITORY, repository) 83 | return intent 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KArchi 2 | This repository is inspired by [Archi](https://github.com/ivacf/archi) by [ivacf](https://github.com/ivacf). 3 | 4 | This is showcases and compares different architectural patterns, all with Java and Kotlin versions. The exact same sample app is built 6 times using the following approaches: 5 | * Java 6 | * __Standard Android__: traditional approach with layouts, Activities/Fragments and Model. 7 | * __MVP__: Model View Presenter. 8 | * __MVVM__: Model View ViewModel with data binding. 9 | * Kotlin 10 | * __Standard Android__: traditional approach with layouts, Activities/Fragments and Model. 11 | * __MVP__: Model View Presenter. 12 | * __MVVM__: Model View ViewModel with data binding. 13 | 14 | ## The App 15 | 16 | The sample app displays a list of GitHub public repositories for a given username. Tapping on one of them will open a repository details screen, where more information about the repo can be found. This screen also shows information about the owner of the repository. 17 | 18 | ![Screenshots](images/archi-screenshots.png) 19 | 20 | ### Libraries used 21 | * Java & Kotlin 22 | * AppCompat, CardView and RecyclerView 23 | * Data Binding (only MVVM) 24 | * RxJava & RxAndroid 25 | * Retrofit 2 26 | * Picasso 27 | * Mockito 28 | * Robolectric 29 | * Kotlin 30 | * [Kotlin Android Extensions](https://kotlinlang.org/docs/tutorials/android-plugin.html) 31 | * [kapt](https://kotlinlang.org/docs/reference/kapt.html) 32 | 33 | 34 | ## Standard Android 35 | The `/app` directoy contains the implementation that follows the traditional standard Android approach. This is a couple of layout files, two Activities and the model. The model is exactly the same for the three implementations and it contains: `Repository`, `User` and a retrofit service (`GithubService`). 36 | 37 | With this approach, Activities are in charge of calling the `GithubService`, processing the data and updating the views. They act kind of like a controller in MVC but with some extra responsibilities that should be part of the view. The problem with this standard architecture is that Activities and Fragments can become quite large and very difficult to tests. Hence why I didn't write any unit test for this case. 38 | 39 | ## MVP - Model View Presenter 40 | In `/app-mvp` you will find the sample app implemented following this pattern. When using mvp, Activities and Fragments become part of the view layer and they delegate most of the work to presenters. Each Activity has a matching presenter that handles accessing the model via the `GithubService`. They also notify the Activities when the data is ready to display. Unit testing presenters becomes very easy by mocking the view layer (Activities). 41 | 42 | ## MVVM - Model View ViewModel 43 | This pattern has recently started to gain popularity due to the release of the [data binding library](https://developer.android.com/tools/data-binding/guide.html). You will find the implementation in `/app-mvvm`. In this case, ViewModels retrieve data from the model when requested from the view via data binding. With this pattern, Activities and Fragments become very lightweight. Moreover, writting unit tests becomes easier because the ViewModels are decoupled from the view. 44 | 45 | ## License 46 | 47 | ``` 48 | Licensed under the Apache License, Version 2.0 (the "License"); 49 | you may not use this file except in compliance with the License. 50 | You may obtain a copy of the License at 51 | 52 | http://www.apache.org/licenses/LICENSE-2.0 53 | 54 | Unless required by applicable law or agreed to in writing, software 55 | distributed under the License is distributed on an "AS IS" BASIS, 56 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 57 | See the License for the specific language governing permissions and 58 | limitations under the License. 59 | ``` 60 | -------------------------------------------------------------------------------- /app/src/main/java/uk/ivanc/archi/RepositoryAdapter.java: -------------------------------------------------------------------------------- 1 | package uk.ivanc.archi; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.TextView; 9 | 10 | import java.util.Collections; 11 | import java.util.List; 12 | 13 | import uk.ivanc.archi.model.Repository; 14 | 15 | public class RepositoryAdapter extends RecyclerView.Adapter { 16 | 17 | private List repositories; 18 | private Callback callback; 19 | 20 | public RepositoryAdapter() { 21 | this.repositories = Collections.emptyList(); 22 | } 23 | 24 | public RepositoryAdapter(List repositories) { 25 | this.repositories = repositories; 26 | } 27 | 28 | public void setRepositories(List repositories) { 29 | this.repositories = repositories; 30 | } 31 | 32 | public void setCallback(Callback callback) { 33 | this.callback = callback; 34 | } 35 | 36 | @Override 37 | public RepositoryViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 38 | final View itemView = LayoutInflater.from(parent.getContext()) 39 | .inflate(R.layout.item_repo, parent, false); 40 | final RepositoryViewHolder viewHolder = new RepositoryViewHolder(itemView); 41 | viewHolder.contentLayout.setOnClickListener(new View.OnClickListener() { 42 | @Override 43 | public void onClick(View v) { 44 | if (callback != null) { 45 | callback.onItemClick(viewHolder.repository); 46 | } 47 | } 48 | }); 49 | return viewHolder; 50 | } 51 | 52 | @Override 53 | public void onBindViewHolder(RepositoryViewHolder holder, int position) { 54 | Repository repository = repositories.get(position); 55 | Context context = holder.titleTextView.getContext(); 56 | holder.repository = repository; 57 | holder.titleTextView.setText(repository.name); 58 | holder.descriptionTextView.setText(repository.description); 59 | holder.watchersTextView.setText( 60 | context.getResources().getString(R.string.text_watchers, repository.watchers)); 61 | holder.starsTextView.setText( 62 | context.getResources().getString(R.string.text_stars, repository.stars)); 63 | holder.forksTextView.setText( 64 | context.getResources().getString(R.string.text_forks, repository.forks)); 65 | } 66 | 67 | @Override 68 | public int getItemCount() { 69 | return repositories.size(); 70 | } 71 | 72 | public static class RepositoryViewHolder extends RecyclerView.ViewHolder { 73 | public View contentLayout; 74 | public TextView titleTextView; 75 | public TextView descriptionTextView; 76 | public TextView watchersTextView; 77 | public TextView starsTextView; 78 | public TextView forksTextView; 79 | public Repository repository; 80 | 81 | public RepositoryViewHolder(View itemView) { 82 | super(itemView); 83 | contentLayout = itemView.findViewById(R.id.layout_content); 84 | titleTextView = (TextView) itemView.findViewById(R.id.text_repo_title); 85 | descriptionTextView = (TextView) itemView.findViewById(R.id.text_repo_description); 86 | watchersTextView = (TextView) itemView.findViewById(R.id.text_watchers); 87 | starsTextView = (TextView) itemView.findViewById(R.id.text_stars); 88 | forksTextView = (TextView) itemView.findViewById(R.id.text_forks); 89 | } 90 | } 91 | 92 | public interface Callback { 93 | void onItemClick(Repository repository); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /app-mvp/src/main/java/uk/ivanc/archimvp/RepositoryAdapter.java: -------------------------------------------------------------------------------- 1 | package uk.ivanc.archimvp; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.TextView; 9 | 10 | import java.util.Collections; 11 | import java.util.List; 12 | 13 | import uk.ivanc.archimvp.model.Repository; 14 | 15 | public class RepositoryAdapter extends RecyclerView.Adapter { 16 | 17 | private List repositories; 18 | private Callback callback; 19 | 20 | public RepositoryAdapter() { 21 | this.repositories = Collections.emptyList(); 22 | } 23 | 24 | public RepositoryAdapter(List repositories) { 25 | this.repositories = repositories; 26 | } 27 | 28 | public void setRepositories(List repositories) { 29 | this.repositories = repositories; 30 | } 31 | 32 | public void setCallback(Callback callback) { 33 | this.callback = callback; 34 | } 35 | 36 | @Override 37 | public RepositoryViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 38 | final View itemView = LayoutInflater.from(parent.getContext()) 39 | .inflate(R.layout.item_repo, parent, false); 40 | final RepositoryViewHolder viewHolder = new RepositoryViewHolder(itemView); 41 | viewHolder.contentLayout.setOnClickListener(new View.OnClickListener() { 42 | @Override 43 | public void onClick(View v) { 44 | if (callback != null) { 45 | callback.onItemClick(viewHolder.repository); 46 | } 47 | } 48 | }); 49 | return viewHolder; 50 | } 51 | 52 | @Override 53 | public void onBindViewHolder(RepositoryViewHolder holder, int position) { 54 | Repository repository = repositories.get(position); 55 | Context context = holder.titleTextView.getContext(); 56 | holder.repository = repository; 57 | holder.titleTextView.setText(repository.name); 58 | holder.descriptionTextView.setText(repository.description); 59 | holder.watchersTextView.setText( 60 | context.getResources().getString(R.string.text_watchers, repository.watchers)); 61 | holder.starsTextView.setText( 62 | context.getResources().getString(R.string.text_stars, repository.stars)); 63 | holder.forksTextView.setText( 64 | context.getResources().getString(R.string.text_forks, repository.forks)); 65 | } 66 | 67 | @Override 68 | public int getItemCount() { 69 | return repositories.size(); 70 | } 71 | 72 | public static class RepositoryViewHolder extends RecyclerView.ViewHolder { 73 | public View contentLayout; 74 | public TextView titleTextView; 75 | public TextView descriptionTextView; 76 | public TextView watchersTextView; 77 | public TextView starsTextView; 78 | public TextView forksTextView; 79 | public Repository repository; 80 | 81 | public RepositoryViewHolder(View itemView) { 82 | super(itemView); 83 | contentLayout = itemView.findViewById(R.id.layout_content); 84 | titleTextView = (TextView) itemView.findViewById(R.id.text_repo_title); 85 | descriptionTextView = (TextView) itemView.findViewById(R.id.text_repo_description); 86 | watchersTextView = (TextView) itemView.findViewById(R.id.text_watchers); 87 | starsTextView = (TextView) itemView.findViewById(R.id.text_stars); 88 | forksTextView = (TextView) itemView.findViewById(R.id.text_forks); 89 | } 90 | } 91 | 92 | public interface Callback { 93 | void onItemClick(Repository repository); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /app-mvvm/src/test/java/uk/ivanc/archimvvm/RepositoryViewModelTest.java: -------------------------------------------------------------------------------- 1 | package uk.ivanc.archimvvm; 2 | 3 | import android.view.View; 4 | 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.robolectric.RobolectricGradleTestRunner; 9 | import org.robolectric.RuntimeEnvironment; 10 | import org.robolectric.annotation.Config; 11 | 12 | import rx.Observable; 13 | import rx.schedulers.Schedulers; 14 | import uk.ivanc.archimvvm.model.GithubService; 15 | import uk.ivanc.archimvvm.model.Repository; 16 | import uk.ivanc.archimvvm.model.User; 17 | import uk.ivanc.archimvvm.util.MockModelFabric; 18 | import uk.ivanc.archimvvm.viewmodel.RepositoryViewModel; 19 | 20 | import static org.junit.Assert.assertEquals; 21 | import static org.mockito.Mockito.mock; 22 | import static org.mockito.Mockito.when; 23 | 24 | @RunWith(RobolectricGradleTestRunner.class) 25 | @Config(constants = BuildConfig.class, sdk = 21) 26 | public class RepositoryViewModelTest { 27 | 28 | GithubService githubService; 29 | ArchiApplication application; 30 | Repository repository; 31 | User owner; 32 | RepositoryViewModel viewModel; 33 | 34 | @Before 35 | public void setUp() { 36 | githubService = mock(GithubService.class); 37 | application = (ArchiApplication) RuntimeEnvironment.application; 38 | // Mock the retrofit service so we don't call the API directly 39 | application.setGithubService(githubService); 40 | // Change the default subscribe schedulers so all observables 41 | // will now run on the same thread 42 | application.setDefaultSubscribeScheduler(Schedulers.immediate()); 43 | // Default behaviour is to load a mock owner when the view model is instantiated 44 | repository = MockModelFabric.newRepository("Repository"); 45 | owner = MockModelFabric.newUser("owner"); 46 | when(githubService.userFromUrl(repository.owner.url)) 47 | .thenReturn(Observable.just(owner)); 48 | viewModel = new RepositoryViewModel(application, repository); 49 | } 50 | 51 | @Test 52 | public void shouldGetDescription() { 53 | assertEquals(repository.description, viewModel.getDescription()); 54 | } 55 | 56 | @Test 57 | public void shouldGetHomepage() { 58 | assertEquals(repository.homepage, viewModel.getHomepage()); 59 | } 60 | 61 | @Test 62 | public void shouldGetLanguage() { 63 | assertEquals(application.getString(R.string.text_language, repository.language), 64 | viewModel.getLanguage()); 65 | } 66 | 67 | @Test 68 | public void shouldReturnHomepageVisibilityGone() { 69 | repository.homepage = null; 70 | assertEquals(View.GONE, viewModel.getHomepageVisibility()); 71 | } 72 | 73 | @Test 74 | public void shouldReturnLanguageVisibilityGone() { 75 | repository.language = null; 76 | assertEquals(View.GONE, viewModel.getLanguageVisibility()); 77 | } 78 | 79 | @Test 80 | public void shouldReturnForkVisibilityVisible() { 81 | repository.fork = true; 82 | assertEquals(View.VISIBLE, viewModel.getForkVisibility()); 83 | } 84 | 85 | @Test 86 | public void shouldReturnForkVisibilityGone() { 87 | repository.fork = false; 88 | assertEquals(View.GONE, viewModel.getForkVisibility()); 89 | } 90 | 91 | @Test 92 | public void shouldLoadFullOwnerOnInstantiation() { 93 | assertEquals(owner.name, viewModel.ownerName.get()); 94 | assertEquals(owner.email, viewModel.ownerEmail.get()); 95 | assertEquals(owner.location, viewModel.ownerLocation.get()); 96 | assertEquals(View.VISIBLE, viewModel.ownerEmailVisibility.get()); 97 | assertEquals(View.VISIBLE, viewModel.ownerLocationVisibility.get()); 98 | assertEquals(View.VISIBLE, viewModel.ownerLayoutVisibility.get()); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /kapp-mvvm/src/test/java/com/taishi/kapp_mvvm/RepositoryViewModelTest.java: -------------------------------------------------------------------------------- 1 | package com.taishi.kapp_mvvm; 2 | 3 | import android.view.View; 4 | 5 | import com.taishi.kapp_mvvm.model.GithubService; 6 | import com.taishi.kapp_mvvm.model.Repository; 7 | import com.taishi.kapp_mvvm.model.User; 8 | import com.taishi.kapp_mvvm.viewmodel.RepositoryViewModel; 9 | 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | import org.junit.runner.RunWith; 13 | import org.robolectric.RobolectricGradleTestRunner; 14 | import org.robolectric.RuntimeEnvironment; 15 | import org.robolectric.annotation.Config; 16 | 17 | import jp.nagisa.kapp_mvvm.BuildConfig; 18 | import jp.nagisa.kapp_mvvm.R; 19 | import rx.Observable; 20 | import rx.schedulers.Schedulers; 21 | 22 | import static org.mockito.Mockito.mock; 23 | import static org.mockito.Mockito.when; 24 | 25 | @RunWith(RobolectricGradleTestRunner.class) 26 | @Config(constants = BuildConfig.class, sdk = 21) 27 | public class RepositoryViewModelTest { 28 | 29 | GithubService githubService; 30 | ArchiApplication application; 31 | Repository repository; 32 | User owner; 33 | RepositoryViewModel viewModel; 34 | 35 | @Before 36 | public void setUp() { 37 | githubService = mock(GithubService.class); 38 | application = (ArchiApplication) RuntimeEnvironment.application; 39 | // Mock the retrofit service so we don't call the API directly 40 | application.setGithubService(githubService); 41 | // Change the default subscribe schedulers so all observables 42 | // will now run on the same thread 43 | application.setDefaultSubscribeScheduler(Schedulers.immediate()); 44 | // Default behaviour is to load a mock owner when the view model is instantiated 45 | repository = MockModelFabric.newRepository("Repository"); 46 | owner = MockModelFabric.newUser("owner"); 47 | when(githubService.userFromUrl(repository.getOwner().getUrl())) 48 | .thenReturn(Observable.just(owner)); 49 | viewModel = new RepositoryViewModel(application, repository); 50 | } 51 | 52 | @Test 53 | public void shouldGetDescription() { 54 | assertEquals(repository.getDescription(), viewModel.getDescription()); 55 | } 56 | 57 | @Test 58 | public void shouldGetHomepage() { 59 | assertEquals(repository.getHomepage(), viewModel.getHomepage()); 60 | } 61 | 62 | @Test 63 | public void shouldGetLanguage() { 64 | assertEquals(application.getString(R.string.text_language, repository.getLanguage()), 65 | viewModel.getLanguage()); 66 | } 67 | 68 | @Test 69 | public void shouldReturnHomepageVisibilityGone() { 70 | repository.setHomepage(null); 71 | assertEquals(View.GONE, viewModel.getHomepageVisibility()); 72 | } 73 | 74 | @Test 75 | public void shouldReturnLanguageVisibilityGone() { 76 | repository.setLanguage(null); 77 | assertEquals(View.GONE, viewModel.getLanguageVisibility()); 78 | } 79 | 80 | @Test 81 | public void shouldReturnForkVisibilityVisible() { 82 | repository.setIsFork(true); 83 | assertEquals(View.VISIBLE, viewModel.getForkVisibility()); 84 | } 85 | 86 | @Test 87 | public void shouldReturnForkVisibilityGone() { 88 | repository.setIsFork(false); 89 | assertEquals(View.GONE, viewModel.getForkVisibility()); 90 | } 91 | 92 | @Test 93 | public void shouldLoadFullOwnerOnInstantiation() { 94 | assertEquals(owner.getName(), viewModel.getOwnerName().get()); 95 | assertEquals(owner.getEmail(), viewModel.getOwnerEmail().get()); 96 | assertEquals(owner.getLocation(), viewModel.getOwnerLocation().get()); 97 | assertEquals(View.VISIBLE, viewModel.getOwnerEmailVisibility().get()); 98 | assertEquals(View.VISIBLE, viewModel.getOwnerLocationVisibility().get()); 99 | assertEquals(View.VISIBLE, viewModel.getOwnerLayoutVisibility().get()); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /app-mvp/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 15 | 16 | 24 | 25 | 36 | 37 | 47 | 48 | 58 | 59 | 60 | 61 | 69 | 70 | 83 | 84 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 15 | 16 | 24 | 25 | 36 | 37 | 47 | 48 | 58 | 59 | 60 | 61 | 69 | 70 | 83 | 84 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /kapp/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 15 | 16 | 24 | 25 | 36 | 37 | 47 | 48 | 58 | 59 | 60 | 61 | 69 | 70 | 83 | 84 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /kapp-mvp/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 15 | 16 | 24 | 25 | 36 | 37 | 47 | 48 | 58 | 59 | 60 | 61 | 69 | 70 | 83 | 84 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /kapp-mvp/src/test/java/com/taishi/kapp_mvp/MainPresenterTest.java: -------------------------------------------------------------------------------- 1 | package com.taishi.kapp_mvp; 2 | 3 | import com.taishi.kapp_mvp.presenter.MainPresenter; 4 | import com.taishi.kapp_mvp.util.MockModelFabric; 5 | import com.taishi.kapp_mvp.view.MainMvpView; 6 | 7 | import org.junit.After; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.robolectric.RobolectricGradleTestRunner; 12 | import org.robolectric.RuntimeEnvironment; 13 | import org.robolectric.annotation.Config; 14 | 15 | import java.util.Collections; 16 | import java.util.List; 17 | 18 | import okhttp3.ResponseBody; 19 | import retrofit2.Response; 20 | import retrofit2.adapter.rxjava.HttpException; 21 | import rx.Observable; 22 | import rx.schedulers.Schedulers; 23 | 24 | import static org.mockito.Mockito.mock; 25 | import static org.mockito.Mockito.verify; 26 | import static org.mockito.Mockito.when; 27 | 28 | @RunWith(RobolectricGradleTestRunner.class) 29 | @Config(constants = BuildConfig.class, sdk = 21) 30 | public class MainPresenterTest { 31 | 32 | MainPresenter mainPresenter; 33 | MainMvpView mainMvpView; 34 | GithubService githubService; 35 | 36 | @Before 37 | public void setUp() { 38 | ArchiApplication application = (ArchiApplication) RuntimeEnvironment.application; 39 | githubService = mock(GithubService.class); 40 | // Mock the retrofit service so we don't call the API directly 41 | application.setGithubService(githubService); 42 | // Change the default subscribe schedulers so all observables 43 | // will now run on the same thread 44 | application.setDefaultSubscribeScheduler(Schedulers.immediate()); 45 | mainPresenter = new MainPresenter(); 46 | mainMvpView = mock(MainMvpView.class); 47 | when(mainMvpView.getContext()).thenReturn(application); 48 | mainPresenter.attachView(mainMvpView); 49 | } 50 | 51 | @After 52 | public void tearDown() { 53 | mainPresenter.detachView(); 54 | } 55 | 56 | @Test 57 | public void loadRepositoriesCallsShowRepositories() { 58 | String username = "ivacf"; 59 | List repositories = MockModelFabric.newListOfRepositories(10); 60 | when(githubService.publicRepositories(username)) 61 | .thenReturn(Observable.just(repositories)); 62 | 63 | mainPresenter.loadRepositories(username); 64 | verify(mainMvpView).showProgressIndicator(); 65 | verify(mainMvpView).showRepositories(repositories); 66 | } 67 | 68 | @Test 69 | public void loadRepositoriesCallsShowMessage_withEmptyReposString() { 70 | String username = "ivacf"; 71 | when(githubService.publicRepositories(username)) 72 | .thenReturn(Observable.just(Collections.emptyList())); 73 | 74 | mainPresenter.loadRepositories(username); 75 | verify(mainMvpView).showProgressIndicator(); 76 | verify(mainMvpView).showMessage(R.string.text_empty_repos); 77 | } 78 | 79 | @Test 80 | public void loadRepositoriesCallsShowMessage_withDefaultErrorString() { 81 | String username = "ivacf"; 82 | when(githubService.publicRepositories(username)) 83 | .thenReturn(Observable.>error(new RuntimeException("error"))); 84 | 85 | mainPresenter.loadRepositories(username); 86 | verify(mainMvpView).showProgressIndicator(); 87 | verify(mainMvpView).showMessage(R.string.error_loading_repos); 88 | } 89 | 90 | @Test 91 | public void loadRepositoriesCallsShowMessage_withUsernameNotFoundString() { 92 | String username = "ivacf"; 93 | HttpException mockHttpException = 94 | new HttpException(Response.error(404, mock(ResponseBody.class))); 95 | when(githubService.publicRepositories(username)) 96 | .thenReturn(Observable.>error(mockHttpException)); 97 | 98 | mainPresenter.loadRepositories(username); 99 | verify(mainMvpView).showProgressIndicator(); 100 | verify(mainMvpView).showMessage(R.string.error_username_not_found); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /app-mvvm/src/test/java/uk/ivanc/archimvvm/ItemRepoViewModelTest.java: -------------------------------------------------------------------------------- 1 | package uk.ivanc.archimvvm; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.databinding.Observable; 6 | import android.view.View; 7 | 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.robolectric.RobolectricGradleTestRunner; 12 | import org.robolectric.RuntimeEnvironment; 13 | import org.robolectric.annotation.Config; 14 | 15 | import uk.ivanc.archimvvm.model.Repository; 16 | import uk.ivanc.archimvvm.viewmodel.ItemRepoViewModel; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | import static org.mockito.Matchers.any; 20 | import static org.mockito.Matchers.anyInt; 21 | import static org.mockito.Mockito.mock; 22 | import static org.mockito.Mockito.verify; 23 | 24 | @RunWith(RobolectricGradleTestRunner.class) 25 | @Config(constants = BuildConfig.class, sdk = 21) 26 | public class ItemRepoViewModelTest { 27 | 28 | ArchiApplication application; 29 | 30 | @Before 31 | public void setUp() { 32 | application = (ArchiApplication) RuntimeEnvironment.application; 33 | } 34 | 35 | @Test 36 | public void shouldGetName() { 37 | Repository repository = new Repository(); 38 | repository.name = "ivan"; 39 | ItemRepoViewModel itemRepoViewModel = new ItemRepoViewModel(application, repository); 40 | assertEquals(repository.name, itemRepoViewModel.getName()); 41 | } 42 | 43 | @Test 44 | public void shouldGetDescription() { 45 | Repository repository = new Repository(); 46 | repository.description = "This is the description"; 47 | ItemRepoViewModel itemRepoViewModel = new ItemRepoViewModel(application, repository); 48 | assertEquals(repository.description, itemRepoViewModel.getDescription()); 49 | } 50 | 51 | @Test 52 | public void shouldGetStars() { 53 | Repository repository = new Repository(); 54 | repository.stars = 10; 55 | String expectedString = application.getString(R.string.text_stars, repository.stars); 56 | ItemRepoViewModel itemRepoViewModel = new ItemRepoViewModel(application, repository); 57 | assertEquals(expectedString, itemRepoViewModel.getStars()); 58 | } 59 | 60 | @Test 61 | public void shouldGetForks() { 62 | Repository repository = new Repository(); 63 | repository.forks = 5; 64 | String expectedString = application.getString(R.string.text_forks, repository.forks); 65 | 66 | ItemRepoViewModel itemRepoViewModel = new ItemRepoViewModel(application, repository); 67 | assertEquals(expectedString, itemRepoViewModel.getForks()); 68 | } 69 | 70 | @Test 71 | public void shouldGetWatchers() { 72 | Repository repository = new Repository(); 73 | repository.watchers = 7; 74 | String expectedString = application.getString(R.string.text_watchers, repository.watchers); 75 | 76 | ItemRepoViewModel itemRepoViewModel = new ItemRepoViewModel(application, repository); 77 | assertEquals(expectedString, itemRepoViewModel.getWatchers()); 78 | } 79 | 80 | @Test 81 | public void shouldStartActivityOnItemClick() { 82 | Repository repository = new Repository(); 83 | Context mockContext = mock(Context.class); 84 | ItemRepoViewModel itemRepoViewModel = new ItemRepoViewModel(mockContext, repository); 85 | itemRepoViewModel.onItemClick(new View(application)); 86 | verify(mockContext).startActivity(any(Intent.class)); 87 | } 88 | 89 | @Test 90 | public void shouldNotifyPropertyChangeWhenSetRepository() { 91 | Repository repository = new Repository(); 92 | ItemRepoViewModel itemRepoViewModel = new ItemRepoViewModel(application, repository); 93 | Observable.OnPropertyChangedCallback mockCallback = 94 | mock(Observable.OnPropertyChangedCallback.class); 95 | itemRepoViewModel.addOnPropertyChangedCallback(mockCallback); 96 | 97 | itemRepoViewModel.setRepository(repository); 98 | verify(mockCallback).onPropertyChanged(any(Observable.class), anyInt()); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /kapp-mvvm/src/test/java/com/taishi/kapp_mvvm/ItemRepoViewModelTest.java: -------------------------------------------------------------------------------- 1 | package com.taishi.kapp_mvvm; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.databinding.Observable; 6 | import android.view.View; 7 | 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.robolectric.RobolectricGradleTestRunner; 12 | import org.robolectric.RuntimeEnvironment; 13 | import org.robolectric.annotation.Config; 14 | 15 | 16 | import com.taishi.kapp_mvvm.model.Repository; 17 | import com.taishi.kapp_mvvm.viewmodel.ItemRepoViewModel; 18 | 19 | 20 | import static org.junit.Assert.assertEquals; 21 | import static org.mockito.Matchers.any; 22 | import static org.mockito.Matchers.anyInt; 23 | import static org.mockito.Mockito.mock; 24 | import static org.mockito.Mockito.verify; 25 | 26 | @RunWith(RobolectricGradleTestRunner.class) 27 | @Config(constants = BuildConfig.class, sdk = 21) 28 | public class ItemRepoViewModelTest { 29 | 30 | ArchiApplication application; 31 | 32 | @Before 33 | public void setUp() { 34 | application = (ArchiApplication) RuntimeEnvironment.application; 35 | } 36 | 37 | @Test 38 | public void shouldGetName() { 39 | Repository repository = new Repository(); 40 | repository.name = "ivan"; 41 | ItemRepoViewModel itemRepoViewModel = new ItemRepoViewModel(application, repository); 42 | assertEquals(repository.name, itemRepoViewModel.getName()); 43 | } 44 | 45 | @Test 46 | public void shouldGetDescription() { 47 | Repository repository = new Repository(); 48 | repository.description = "This is the description"; 49 | ItemRepoViewModel itemRepoViewModel = new ItemRepoViewModel(application, repository); 50 | assertEquals(repository.description, itemRepoViewModel.getDescription()); 51 | } 52 | 53 | @Test 54 | public void shouldGetStars() { 55 | Repository repository = new Repository(); 56 | repository.stars = 10; 57 | String expectedString = application.getString(R.string.text_stars, repository.stars); 58 | ItemRepoViewModel itemRepoViewModel = new ItemRepoViewModel(application, repository); 59 | assertEquals(expectedString, itemRepoViewModel.getStars()); 60 | } 61 | 62 | @Test 63 | public void shouldGetForks() { 64 | Repository repository = new Repository(); 65 | repository.forks = 5; 66 | String expectedString = application.getString(R.string.text_forks, repository.forks); 67 | 68 | ItemRepoViewModel itemRepoViewModel = new ItemRepoViewModel(application, repository); 69 | assertEquals(expectedString, itemRepoViewModel.getForks()); 70 | } 71 | 72 | @Test 73 | public void shouldGetWatchers() { 74 | Repository repository = new Repository(); 75 | repository.watchers = 7; 76 | String expectedString = application.getString(R.string.text_watchers, repository.watchers); 77 | 78 | ItemRepoViewModel itemRepoViewModel = new ItemRepoViewModel(application, repository); 79 | assertEquals(expectedString, itemRepoViewModel.getWatchers()); 80 | } 81 | 82 | @Test 83 | public void shouldStartActivityOnItemClick() { 84 | Repository repository = new Repository(); 85 | Context mockContext = mock(Context.class); 86 | ItemRepoViewModel itemRepoViewModel = new ItemRepoViewModel(mockContext, repository); 87 | itemRepoViewModel.onItemClick(new View(application)); 88 | verify(mockContext).startActivity(any(Intent.class)); 89 | } 90 | 91 | @Test 92 | public void shouldNotifyPropertyChangeWhenSetRepository() { 93 | Repository repository = new Repository(); 94 | ItemRepoViewModel itemRepoViewModel = new ItemRepoViewModel(application, repository); 95 | Observable.OnPropertyChangedCallback mockCallback = 96 | mock(Observable.OnPropertyChangedCallback.class); 97 | itemRepoViewModel.addOnPropertyChangedCallback(mockCallback); 98 | 99 | itemRepoViewModel.setRepository(repository); 100 | verify(mockCallback).onPropertyChanged(any(Observable.class), anyInt()); 101 | } 102 | } 103 | --------------------------------------------------------------------------------