diff --git a/.gitignore b/.gitignore index 39fb081..2c41655 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,8 @@ *.iml .gradle /local.properties -/.idea/workspace.xml -/.idea/libraries .DS_Store /build /captures .externalNativeBuild +.idea \ No newline at end of file diff --git a/.idea/__vcs.xml b/.idea/__vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/__vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml deleted file mode 100644 index 7ac24c7..0000000 --- a/.idea/gradle.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index fee3a84..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index bd4fb0f..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml deleted file mode 100644 index 7f68460..0000000 --- a/.idea/runConfigurations.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index ceb69ee..cd237e6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,20 +1,14 @@ -project.ext { - appcompat = "25.3.1" - arch = "1.0.0-alpha1" - retrofit = "2.0.2" - constraintLayout = "1.0.2" - dagger_version = "2.11" -} - apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' android { - compileSdkVersion 25 - buildToolsVersion "25.0.3" + compileSdkVersion 27 + buildToolsVersion "27.0.1" defaultConfig { applicationId "com.example.test.mvvm_sample_app" minSdkVersion 19 - targetSdkVersion 25 + targetSdkVersion 27 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" @@ -25,7 +19,6 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } - dataBinding { enabled = true } @@ -35,30 +28,55 @@ android { } } +project.ext { + appcompat = "27.0.2" + arch = "1.0.0" + retrofit = "2.3.0" + constraintLayout = "1.0.2" + dagger_version = "2.13" +} + dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) - androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { - exclude group: 'com.android.support', module: 'support-annotations' - }) + // kotlin + compile "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + kapt "com.android.databinding:compiler:3.1.0-alpha08" + // google support libraries compile "com.android.support:appcompat-v7:$project.appcompat" compile "com.android.support:cardview-v7:$project.appcompat" compile "com.android.support:recyclerview-v7:$project.appcompat" + compile "com.android.support.constraint:constraint-layout:$project.constraintLayout" + compile "com.android.support:support-v4:$project.appcompat" + compile "com.android.support:design:$project.appcompat" + + // architecture components compile "android.arch.lifecycle:runtime:$project.arch" compile "android.arch.lifecycle:extensions:$project.arch" + kapt "android.arch.lifecycle:compiler:$project.arch" + + // network libraries compile "com.squareup.retrofit2:retrofit:$project.retrofit" compile "com.squareup.retrofit2:converter-gson:$project.retrofit" - annotationProcessor "android.arch.lifecycle:compiler:$project.arch" - compile "com.android.support.constraint:constraint-layout:$project.constraintLayout" - compile "com.android.support:support-v4:$project.appcompat" + // dagger2 dependency injection compile "com.google.dagger:dagger:$project.dagger_version" compile "com.google.dagger:dagger-android:$project.dagger_version" compile "com.google.dagger:dagger-android-support:$project.dagger_version" + kapt "com.google.dagger:dagger-android-processor:$dagger_version" + kapt "com.google.dagger:dagger-compiler:$dagger_version" - annotationProcessor "com.google.dagger:dagger-android-processor:$dagger_version" - annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version" - + // test libraries testCompile 'junit:junit:4.12' - compile 'com.android.support:design:25.3.1' -} + androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { + exclude group: 'com.android.support', module: 'support-annotations' + }) + androidTestCompile('com.android.support.test.espresso:espresso-contrib:2.2') { + // Necessary to avoid version conflicts + exclude group: 'com.android.support', module: 'appcompat' + exclude group: 'com.android.support', module: 'support-v4' + exclude group: 'com.android.support', module: 'support-annotations' + exclude module: 'recyclerview-v7' + } + +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/example/test/mvvmsampleapp/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/example/test/mvvmsampleapp/ExampleInstrumentedTest.java deleted file mode 100644 index 27bdca3..0000000 --- a/app/src/androidTest/java/com/example/test/mvvmsampleapp/ExampleInstrumentedTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.example.test.mvvmsampleapp; - -import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.junit.Assert.*; - -/** - * Instrumentation test, which will execute on an Android device. - * - * @see Testing documentation - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - @Test - public void useAppContext() throws Exception { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getTargetContext(); - - assertEquals("com.example.test.mvvm_sample_app", appContext.getPackageName()); - } -} diff --git a/app/src/androidTest/java/com/example/test/mvvmsampleapp/UiTests.kt b/app/src/androidTest/java/com/example/test/mvvmsampleapp/UiTests.kt new file mode 100644 index 0000000..78f6e40 --- /dev/null +++ b/app/src/androidTest/java/com/example/test/mvvmsampleapp/UiTests.kt @@ -0,0 +1,52 @@ +package com.example.test.mvvmsampleapp + +import android.support.test.espresso.Espresso.onView +import android.support.test.espresso.action.ViewActions.click +import android.support.test.espresso.assertion.ViewAssertions.matches +import android.support.test.espresso.contrib.RecyclerViewActions +import android.support.test.espresso.matcher.ViewMatchers.isDisplayed +import android.support.test.espresso.matcher.ViewMatchers.withId +import android.support.test.rule.ActivityTestRule +import android.support.test.runner.AndroidJUnit4 +import com.example.test.mvvmsampleapp.view.ui.MainActivity +import com.example.test.mvvmsampleapp.view.viewholders.ProjectViewHolder +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + + + + + +/** + * Instrumentation test, which will execute on an Android device. + * + * @see [Testing documentation](http://d.android.com/tools/testing) + */ +@RunWith(AndroidJUnit4::class) +class UiTests { + + @Rule + @JvmField + var mainActivityTestRule = ActivityTestRule(MainActivity::class.java) + + /** + * Lazy example test, phone should be connected to the internet. + * Ideally you need to implement a mock web server. + */ + @Before + fun waitForLoading() { + onView(withId(R.id.loading_projects)).check(matches(isDisplayed())) + Thread.sleep(1000) + } + + @Test + fun clickFirstElement() { + onView(withId(R.id.project_list)) + .perform(RecyclerViewActions.actionOnItemAtPosition(0, click())) + + onView(withId(R.id.imageView)).check(matches(isDisplayed())) + } + +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6d7a09c..fc31468 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -5,7 +5,7 @@ + + override fun onCreate() { + super.onCreate() + AppInjector.init(this) + } + + override fun activityInjector(): DispatchingAndroidInjector? { + return dispatchingAndroidInjector + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/MVVMApplication.java b/app/src/main/java/com/example/test/mvvmsampleapp/MVVMApplication.java deleted file mode 100644 index a8911f4..0000000 --- a/app/src/main/java/com/example/test/mvvmsampleapp/MVVMApplication.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.example.test.mvvmsampleapp; - -import android.app.Activity; -import android.app.Application; - -import com.example.test.mvvmsampleapp.di.AppInjector; - -import javax.inject.Inject; - -import dagger.android.DispatchingAndroidInjector; -import dagger.android.HasActivityInjector; - -public class MVVMApplication extends Application implements HasActivityInjector { - - @Inject - DispatchingAndroidInjector dispatchingAndroidInjector; - - @Override - public void onCreate() { - super.onCreate(); - AppInjector.init(this); - } - - @Override - public DispatchingAndroidInjector activityInjector() { - return dispatchingAndroidInjector; - } -} diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/di/AppComponent.java b/app/src/main/java/com/example/test/mvvmsampleapp/di/AppComponent.java deleted file mode 100644 index b3c73ab..0000000 --- a/app/src/main/java/com/example/test/mvvmsampleapp/di/AppComponent.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.example.test.mvvmsampleapp.di; - -import com.example.test.mvvmsampleapp.MVVMApplication; -import android.app.Application; -import javax.inject.Singleton; - -import dagger.BindsInstance; -import dagger.Component; -import dagger.android.AndroidInjectionModule; - -@Singleton -@Component(modules = { - AndroidInjectionModule.class, - AppModule.class, - MainActivityModule.class -}) -public interface AppComponent { - @Component.Builder - interface Builder { - @BindsInstance Builder application(Application application); - AppComponent build(); - } - void inject(MVVMApplication mvvmApplication); -} diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/di/AppComponent.kt b/app/src/main/java/com/example/test/mvvmsampleapp/di/AppComponent.kt new file mode 100644 index 0000000..eea4d7a --- /dev/null +++ b/app/src/main/java/com/example/test/mvvmsampleapp/di/AppComponent.kt @@ -0,0 +1,22 @@ +package com.example.test.mvvmsampleapp.di + +import android.app.Application +import com.example.test.mvvmsampleapp.App +import dagger.BindsInstance +import dagger.Component +import dagger.android.AndroidInjectionModule +import javax.inject.Singleton + +@Singleton +@Component(modules = [(AndroidInjectionModule::class), (AppModule::class), (MainActivityModule::class)]) +interface AppComponent { + @Component.Builder + interface Builder { + @BindsInstance + fun application(application: Application): Builder + + fun build(): AppComponent + } + + fun inject(app: App) +} diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/di/AppInjector.java b/app/src/main/java/com/example/test/mvvmsampleapp/di/AppInjector.java deleted file mode 100644 index a8a3be7..0000000 --- a/app/src/main/java/com/example/test/mvvmsampleapp/di/AppInjector.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.example.test.mvvmsampleapp.di; - -import android.app.Activity; -import android.app.Application; -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentActivity; -import android.support.v4.app.FragmentManager; - -import com.example.test.mvvmsampleapp.MVVMApplication; - -import dagger.android.AndroidInjection; -import dagger.android.support.AndroidSupportInjection; -import dagger.android.support.HasSupportFragmentInjector; - -/** - * AppInjector is a helper class to automatically inject fragments if they implement {@link Injectable}. - */ -public class AppInjector { - private AppInjector() {} - - public static void init(MVVMApplication mvvmApplication) { - DaggerAppComponent.builder().application(mvvmApplication) - .build().inject(mvvmApplication); - - mvvmApplication - .registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() { - @Override - public void onActivityCreated(Activity activity, Bundle savedInstanceState) { - handleActivity(activity); - } - - @Override - public void onActivityStarted(Activity activity) { - - } - - @Override - public void onActivityResumed(Activity activity) { - - } - - @Override - public void onActivityPaused(Activity activity) { - - } - - @Override - public void onActivityStopped(Activity activity) { - - } - - @Override - public void onActivitySaveInstanceState(Activity activity, Bundle outState) { - - } - - @Override - public void onActivityDestroyed(Activity activity) { - - } - }); - } - - private static void handleActivity(Activity activity) { - if (activity instanceof HasSupportFragmentInjector) { - AndroidInjection.inject(activity); - } - if (activity instanceof FragmentActivity) { - ((FragmentActivity) activity).getSupportFragmentManager() - .registerFragmentLifecycleCallbacks( - new FragmentManager.FragmentLifecycleCallbacks() { - @Override - public void onFragmentCreated(FragmentManager fm, Fragment fragment, - Bundle savedInstanceState) { - if (fragment instanceof Injectable) { - AndroidSupportInjection.inject(fragment); - } - } - }, true); - } - } -} diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/di/AppInjector.kt b/app/src/main/java/com/example/test/mvvmsampleapp/di/AppInjector.kt new file mode 100644 index 0000000..f292a15 --- /dev/null +++ b/app/src/main/java/com/example/test/mvvmsampleapp/di/AppInjector.kt @@ -0,0 +1,73 @@ +package com.example.test.mvvmsampleapp.di + +import android.app.Activity +import android.app.Application +import android.os.Bundle +import android.support.v4.app.Fragment +import android.support.v4.app.FragmentActivity +import android.support.v4.app.FragmentManager + +import com.example.test.mvvmsampleapp.App + +import dagger.android.AndroidInjection +import dagger.android.support.AndroidSupportInjection +import dagger.android.support.HasSupportFragmentInjector + +/** + * AppInjector is a helper class to automatically inject fragments if they implement [Injectable]. + */ +object AppInjector { + + fun init(app: App) { + DaggerAppComponent + .builder() + .application(app) + .build() + .inject(app) + + app.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks { + override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { + handleActivity(activity) + } + + override fun onActivityStarted(activity: Activity) { + + } + + override fun onActivityResumed(activity: Activity) { + + } + + override fun onActivityPaused(activity: Activity) { + + } + + override fun onActivityStopped(activity: Activity) { + + } + + override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle?) { + + } + + override fun onActivityDestroyed(activity: Activity) { + + } + }) + } + + private fun handleActivity(activity: Activity) { + if (activity is HasSupportFragmentInjector) { + AndroidInjection.inject(activity) + } + (activity as? FragmentActivity)?.supportFragmentManager?.registerFragmentLifecycleCallbacks( + object : FragmentManager.FragmentLifecycleCallbacks() { + override fun onFragmentCreated(fm: FragmentManager?, fragment: Fragment?, + savedInstanceState: Bundle?) { + if (fragment is Injectable) { + AndroidSupportInjection.inject(fragment) + } + } + }, true) + } +} diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/di/AppModule.java b/app/src/main/java/com/example/test/mvvmsampleapp/di/AppModule.java deleted file mode 100644 index 3070bfc..0000000 --- a/app/src/main/java/com/example/test/mvvmsampleapp/di/AppModule.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.example.test.mvvmsampleapp.di; - -import android.arch.lifecycle.ViewModelProvider; - -import com.example.test.mvvmsampleapp.service.repository.GitHubService; -import com.example.test.mvvmsampleapp.viewmodel.ProjectViewModelFactory; - -import javax.inject.Singleton; - -import dagger.Module; -import dagger.Provides; -import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; - -@Module(subcomponents = ViewModelSubComponent.class) -class AppModule { - @Singleton @Provides - GitHubService provideGithubService() { - return new Retrofit.Builder() - .baseUrl(GitHubService.HTTPS_API_GITHUB_URL) - .addConverterFactory(GsonConverterFactory.create()) - .build() - .create(GitHubService.class); - } - - @Singleton - @Provides - ViewModelProvider.Factory provideViewModelFactory( - ViewModelSubComponent.Builder viewModelSubComponent) { - - return new ProjectViewModelFactory(viewModelSubComponent.build()); - } -} diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/di/AppModule.kt b/app/src/main/java/com/example/test/mvvmsampleapp/di/AppModule.kt new file mode 100644 index 0000000..94c6444 --- /dev/null +++ b/app/src/main/java/com/example/test/mvvmsampleapp/di/AppModule.kt @@ -0,0 +1,32 @@ +package com.example.test.mvvmsampleapp.di + +import android.arch.lifecycle.ViewModelProvider +import com.example.test.mvvmsampleapp.service.repository.GitHubService +import com.example.test.mvvmsampleapp.viewmodel.ProjectViewModelFactory +import dagger.Module +import dagger.Provides +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import javax.inject.Singleton + +@Module(subcomponents = [(ViewModelSubComponent::class)]) +internal class AppModule { + + @Singleton + @Provides + fun provideGithubService(): GitHubService { + return Retrofit.Builder() + .baseUrl(GitHubService.HTTPS_API_GITHUB_URL) + .addConverterFactory(GsonConverterFactory.create()) + .build() + .create(GitHubService::class.java) + } + + @Singleton + @Provides + fun provideViewModelFactory( + viewModelSubComponent: ViewModelSubComponent.Builder): ViewModelProvider.Factory { + + return ProjectViewModelFactory(viewModelSubComponent.build()) + } +} diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/di/FragmentBuildersModule.java b/app/src/main/java/com/example/test/mvvmsampleapp/di/FragmentBuildersModule.java deleted file mode 100644 index a8fc351..0000000 --- a/app/src/main/java/com/example/test/mvvmsampleapp/di/FragmentBuildersModule.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.example.test.mvvmsampleapp.di; - -import com.example.test.mvvmsampleapp.view.ui.ProjectFragment; -import com.example.test.mvvmsampleapp.view.ui.ProjectListFragment; - -import dagger.Module; -import dagger.android.ContributesAndroidInjector; - -@Module -public abstract class FragmentBuildersModule { - @ContributesAndroidInjector - abstract ProjectFragment contributeProjectFragment(); - - @ContributesAndroidInjector - abstract ProjectListFragment contributeProjectListFragment(); -} diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/di/FragmentBuildersModule.kt b/app/src/main/java/com/example/test/mvvmsampleapp/di/FragmentBuildersModule.kt new file mode 100644 index 0000000..277b7dc --- /dev/null +++ b/app/src/main/java/com/example/test/mvvmsampleapp/di/FragmentBuildersModule.kt @@ -0,0 +1,18 @@ +package com.example.test.mvvmsampleapp.di + +import com.example.test.mvvmsampleapp.view.ui.ProjectFragment +import com.example.test.mvvmsampleapp.view.ui.ProjectListFragment + +import dagger.Module +import dagger.android.ContributesAndroidInjector + +@Module +abstract class FragmentBuildersModule { + + @ContributesAndroidInjector + internal abstract fun contributeProjectFragment(): ProjectFragment + + @ContributesAndroidInjector + internal abstract fun contributeProjectListFragment(): ProjectListFragment + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/di/Injectable.java b/app/src/main/java/com/example/test/mvvmsampleapp/di/Injectable.java deleted file mode 100644 index 57c83b7..0000000 --- a/app/src/main/java/com/example/test/mvvmsampleapp/di/Injectable.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.example.test.mvvmsampleapp.di; - -/** - * Marker interface for fragments. - */ -public interface Injectable { -} diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/di/Injectable.kt b/app/src/main/java/com/example/test/mvvmsampleapp/di/Injectable.kt new file mode 100644 index 0000000..3042210 --- /dev/null +++ b/app/src/main/java/com/example/test/mvvmsampleapp/di/Injectable.kt @@ -0,0 +1,6 @@ +package com.example.test.mvvmsampleapp.di + +/** + * Marker interface for fragments. + */ +interface Injectable \ No newline at end of file diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/di/MainActivityModule.java b/app/src/main/java/com/example/test/mvvmsampleapp/di/MainActivityModule.java deleted file mode 100644 index 6314631..0000000 --- a/app/src/main/java/com/example/test/mvvmsampleapp/di/MainActivityModule.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.example.test.mvvmsampleapp.di; - -import com.example.test.mvvmsampleapp.view.ui.MainActivity; - -import dagger.Module; -import dagger.android.ContributesAndroidInjector; - -@Module -public abstract class MainActivityModule { - @ContributesAndroidInjector(modules = FragmentBuildersModule.class) - abstract MainActivity contributeMainActivity(); -} diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/di/MainActivityModule.kt b/app/src/main/java/com/example/test/mvvmsampleapp/di/MainActivityModule.kt new file mode 100644 index 0000000..e5639db --- /dev/null +++ b/app/src/main/java/com/example/test/mvvmsampleapp/di/MainActivityModule.kt @@ -0,0 +1,14 @@ +package com.example.test.mvvmsampleapp.di + +import com.example.test.mvvmsampleapp.view.ui.MainActivity + +import dagger.Module +import dagger.android.ContributesAndroidInjector + +@Module +abstract class MainActivityModule { + + @ContributesAndroidInjector(modules = [(FragmentBuildersModule::class)]) + internal abstract fun contributeMainActivity(): MainActivity + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/di/ViewModelSubComponent.java b/app/src/main/java/com/example/test/mvvmsampleapp/di/ViewModelSubComponent.java deleted file mode 100644 index 5999ff5..0000000 --- a/app/src/main/java/com/example/test/mvvmsampleapp/di/ViewModelSubComponent.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.example.test.mvvmsampleapp.di; - -import com.example.test.mvvmsampleapp.viewmodel.ProjectListViewModel; -import com.example.test.mvvmsampleapp.viewmodel.ProjectViewModel; - -import dagger.Subcomponent; - -/** - * A sub component to create ViewModels. It is called by the - * {@link com.example.test.mvvmsampleapp.viewmodel.ProjectViewModelFactory}. - */ -@Subcomponent -public interface ViewModelSubComponent { - @Subcomponent.Builder - interface Builder { - ViewModelSubComponent build(); - } - - ProjectListViewModel projectListViewModel(); - ProjectViewModel projectViewModel(); -} diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/di/ViewModelSubComponent.kt b/app/src/main/java/com/example/test/mvvmsampleapp/di/ViewModelSubComponent.kt new file mode 100644 index 0000000..ddd7d79 --- /dev/null +++ b/app/src/main/java/com/example/test/mvvmsampleapp/di/ViewModelSubComponent.kt @@ -0,0 +1,24 @@ +package com.example.test.mvvmsampleapp.di + +import com.example.test.mvvmsampleapp.viewmodel.ProjectListViewModel +import com.example.test.mvvmsampleapp.viewmodel.ProjectViewModel + +import dagger.Subcomponent + +/** + * A sub component to create ViewModels. It is called by the + * [com.example.test.mvvmsampleapp.viewmodel.ProjectViewModelFactory]. + */ +@Subcomponent +interface ViewModelSubComponent { + + @Subcomponent.Builder + interface Builder { + fun build(): ViewModelSubComponent + } + + fun projectListViewModel(): ProjectListViewModel + + fun projectViewModel(): ProjectViewModel + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/service/model/Project.java b/app/src/main/java/com/example/test/mvvmsampleapp/service/model/Project.java deleted file mode 100644 index 040dbc6..0000000 --- a/app/src/main/java/com/example/test/mvvmsampleapp/service/model/Project.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.example.test.mvvmsampleapp.service.model; - -import java.util.Date; - -public class Project { - public long id; - public String name; - public String full_name; - public User owner; - public String html_url; - public String description; - public String url; - public Date created_at; - public Date updated_at; - public Date pushed_at; - public String git_url; - public String ssh_url; - public String clone_url; - public String svn_url; - public String homepage; - public int stargazers_count; - public int watchers_count; - public String language; - public boolean has_issues; - public boolean has_downloads; - public boolean has_wiki; - public boolean has_pages; - public int forks_count; - public int open_issues_count; - public int forks; - public int open_issues; - public int watchers; - public String default_branch; - - public Project() { - } - - public Project(String name) { - this.name = name; - } -} diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/service/model/Project.kt b/app/src/main/java/com/example/test/mvvmsampleapp/service/model/Project.kt new file mode 100644 index 0000000..a552bbd --- /dev/null +++ b/app/src/main/java/com/example/test/mvvmsampleapp/service/model/Project.kt @@ -0,0 +1,33 @@ +package com.example.test.mvvmsampleapp.service.model + +import java.util.* + +data class Project( + var id: Long = 0, + var name: String? = null, + var full_name: String? = null, + var owner: User? = null, + var html_url: String? = null, + var description: String? = null, + var url: String? = null, + var created_at: Date? = null, + var updated_at: Date? = null, + var pushed_at: Date? = null, + var git_url: String? = null, + var ssh_url: String? = null, + var clone_url: String? = null, + var svn_url: String? = null, + var homepage: String? = null, + var stargazers_count: Int = 0, + var watchers_count: Int = 0, + var language: String? = null, + var has_issues: Boolean = false, + var has_downloads: Boolean = false, + var has_wiki: Boolean = false, + var has_pages: Boolean = false, + var forks_count: Int = 0, + var open_issues_count: Int = 0, + var forks: Int = 0, + var open_issues: Int = 0, + var watchers: Int = 0, + var default_branch: String? = null) diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/service/model/User.java b/app/src/main/java/com/example/test/mvvmsampleapp/service/model/User.java deleted file mode 100644 index 367e4bf..0000000 --- a/app/src/main/java/com/example/test/mvvmsampleapp/service/model/User.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.example.test.mvvmsampleapp.service.model; - -import java.util.Date; - -public class User { - public String login; - public long id; - public String avatar_url; - public String gravatar_id; - public String url; - public String html_url; - public String followers_url; - public String following_url; - public String gists_url; - public String starred_url; - public String subscriptions_url; - public String organizations_url; - public String repos_url; - public String events_url; - public String received_events_url; - public String type; - public String name; - public String blog; - public String location; - public String email; - public int public_repos; - public int public_gists; - public int followers; - public int following; - public Date created_at; - public Date updated_at; -} \ No newline at end of file diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/service/model/User.kt b/app/src/main/java/com/example/test/mvvmsampleapp/service/model/User.kt new file mode 100644 index 0000000..26b1dd5 --- /dev/null +++ b/app/src/main/java/com/example/test/mvvmsampleapp/service/model/User.kt @@ -0,0 +1,31 @@ +package com.example.test.mvvmsampleapp.service.model + +import java.util.* + +data class User( + var login: String? = null, + var id: Long = 0, + var avatar_url: String? = null, + var gravatar_id: String? = null, + var url: String? = null, + var html_url: String? = null, + var followers_url: String? = null, + var following_url: String? = null, + var gists_url: String? = null, + var starred_url: String? = null, + var subscriptions_url: String? = null, + var organizations_url: String? = null, + var repos_url: String? = null, + var events_url: String? = null, + var received_events_url: String? = null, + var type: String? = null, + var name: String? = null, + var blog: String? = null, + var location: String? = null, + var email: String? = null, + var public_repos: Int = 0, + var public_gists: Int = 0, + var followers: Int = 0, + var following: Int = 0, + var created_at: Date? = null, + var updated_at: Date? = null) \ No newline at end of file diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/service/repository/GitHubService.java b/app/src/main/java/com/example/test/mvvmsampleapp/service/repository/GitHubService.java deleted file mode 100644 index a1fccc9..0000000 --- a/app/src/main/java/com/example/test/mvvmsampleapp/service/repository/GitHubService.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.example.test.mvvmsampleapp.service.repository; - -import com.example.test.mvvmsampleapp.service.model.Project; - -import java.util.List; - -import retrofit2.Call; -import retrofit2.http.GET; -import retrofit2.http.Path; - -public interface GitHubService { - String HTTPS_API_GITHUB_URL = "https://api.github.com/"; - - @GET("users/{user}/repos") - Call> getProjectList(@Path("user") String user); - - @GET("/repos/{user}/{reponame}") - Call getProjectDetails(@Path("user") String user, @Path("reponame") String projectName); -} diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/service/repository/GitHubService.kt b/app/src/main/java/com/example/test/mvvmsampleapp/service/repository/GitHubService.kt new file mode 100644 index 0000000..7524f9d --- /dev/null +++ b/app/src/main/java/com/example/test/mvvmsampleapp/service/repository/GitHubService.kt @@ -0,0 +1,24 @@ +package com.example.test.mvvmsampleapp.service.repository + +import com.example.test.mvvmsampleapp.service.model.Project + +import retrofit2.Call +import retrofit2.http.GET +import retrofit2.http.Path + +/** + * Network service with a list of all network calls in the app via retrofit. + */ +interface GitHubService { + + @GET("users/{user}/repos") + fun getProjectList(@Path("user") user: String): Call> + + @GET("repos/{user}/{reponame}") + fun getProjectDetails(@Path("user") user: String, @Path("reponame") projectName: String?): Call + + companion object { + val HTTPS_API_GITHUB_URL = "https://api.github.com/" + } + +} diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/service/repository/ProjectRepository.java b/app/src/main/java/com/example/test/mvvmsampleapp/service/repository/ProjectRepository.java deleted file mode 100644 index 4252afe..0000000 --- a/app/src/main/java/com/example/test/mvvmsampleapp/service/repository/ProjectRepository.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.example.test.mvvmsampleapp.service.repository; - -import android.arch.lifecycle.LiveData; -import android.arch.lifecycle.MutableLiveData; - -import com.example.test.mvvmsampleapp.service.model.Project; - -import java.util.List; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -@Singleton -public class ProjectRepository { - private GitHubService gitHubService; - - @Inject - public ProjectRepository(GitHubService gitHubService) { - this.gitHubService = gitHubService; - } - - public LiveData> getProjectList(String userId) { - final MutableLiveData> data = new MutableLiveData<>(); - - gitHubService.getProjectList(userId).enqueue(new Callback>() { - @Override - public void onResponse(Call> call, Response> response) { - data.setValue(response.body()); - } - - @Override - public void onFailure(Call> call, Throwable t) { - // TODO better error handling in part #2 ... - data.setValue(null); - } - }); - - return data; - } - - public LiveData getProjectDetails(String userID, String projectName) { - final MutableLiveData data = new MutableLiveData<>(); - - gitHubService.getProjectDetails(userID, projectName).enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - simulateDelay(); - data.setValue(response.body()); - } - - @Override - public void onFailure(Call call, Throwable t) { - // TODO better error handling in part #2 ... - data.setValue(null); - } - }); - - return data; - } - - private void simulateDelay() { - try { - Thread.sleep(10); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } -} diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/service/repository/ProjectRepository.kt b/app/src/main/java/com/example/test/mvvmsampleapp/service/repository/ProjectRepository.kt new file mode 100644 index 0000000..e909ad9 --- /dev/null +++ b/app/src/main/java/com/example/test/mvvmsampleapp/service/repository/ProjectRepository.kt @@ -0,0 +1,65 @@ +package com.example.test.mvvmsampleapp.service.repository + +import android.arch.lifecycle.LiveData +import android.arch.lifecycle.MutableLiveData +import com.example.test.mvvmsampleapp.service.model.Project +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ProjectRepository @Inject +constructor(private val gitHubService: GitHubService) { + + fun getProjectList(userId: String): LiveData> { + val data = MutableLiveData>() + + gitHubService.getProjectList(userId) + .enqueue(object : Callback> { + override fun onResponse(call: Call>, + response: Response>) { + data.value = response.body() + } + + override fun onFailure(call: Call>, + t: Throwable) { + // TODO better error handling in part #2 ... + data.value = null + } + }) + + return data + } + + fun getProjectDetails(userID: String, projectName: String?): LiveData { + val data = MutableLiveData() + + gitHubService.getProjectDetails(userID, projectName) + .enqueue(object : Callback { + override fun onResponse(call: Call, + response: Response) { + simulateDelay() + data.value = response.body() + } + + override fun onFailure(call: Call, + t: Throwable) { + // TODO better error handling in part #2 ... + data.value = null + } + }) + + return data + } + + private fun simulateDelay() { + try { + Thread.sleep(10) + } catch (e: InterruptedException) { + e.printStackTrace() + } + + } +} diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/view/adapter/CustomBindingAdapter.java b/app/src/main/java/com/example/test/mvvmsampleapp/view/adapter/CustomBindingAdapter.java deleted file mode 100644 index 297147f..0000000 --- a/app/src/main/java/com/example/test/mvvmsampleapp/view/adapter/CustomBindingAdapter.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.example.test.mvvmsampleapp.view.adapter; - -import android.databinding.BindingAdapter; -import android.view.View; - -public class CustomBindingAdapter { - @BindingAdapter("visibleGone") - public static void showHide(View view, boolean show) { - view.setVisibility(show ? View.VISIBLE : View.GONE); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/view/adapter/CustomBindingAdapter.kt b/app/src/main/java/com/example/test/mvvmsampleapp/view/adapter/CustomBindingAdapter.kt new file mode 100644 index 0000000..69849c5 --- /dev/null +++ b/app/src/main/java/com/example/test/mvvmsampleapp/view/adapter/CustomBindingAdapter.kt @@ -0,0 +1,9 @@ +package com.example.test.mvvmsampleapp.view.adapter + +import android.databinding.BindingAdapter +import android.view.View + +@BindingAdapter("visibleGone") +fun showHide(view: View, show: Boolean) { + view.visibility = if (show) View.VISIBLE else View.GONE +} \ No newline at end of file diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/view/adapter/ProjectAdapter.java b/app/src/main/java/com/example/test/mvvmsampleapp/view/adapter/ProjectAdapter.java deleted file mode 100755 index 3618fcf..0000000 --- a/app/src/main/java/com/example/test/mvvmsampleapp/view/adapter/ProjectAdapter.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.example.test.mvvmsampleapp.view.adapter; - -import android.databinding.DataBindingUtil; -import android.support.annotation.Nullable; -import android.support.v7.util.DiffUtil; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.ViewGroup; - -import com.example.test.mvvmsampleapp.R; -import com.example.test.mvvmsampleapp.databinding.ProjectListItemBinding; -import com.example.test.mvvmsampleapp.service.model.Project; -import com.example.test.mvvmsampleapp.view.callback.ProjectClickCallback; - -import java.util.List; -import java.util.Objects; - -public class ProjectAdapter extends RecyclerView.Adapter { - - List projectList; - - @Nullable - private final ProjectClickCallback projectClickCallback; - - public ProjectAdapter(@Nullable ProjectClickCallback projectClickCallback) { - this.projectClickCallback = projectClickCallback; - } - - public void setProjectList(final List projectList) { - if (this.projectList == null) { - this.projectList = projectList; - notifyItemRangeInserted(0, projectList.size()); - } else { - DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() { - @Override - public int getOldListSize() { - return ProjectAdapter.this.projectList.size(); - } - - @Override - public int getNewListSize() { - return projectList.size(); - } - - @Override - public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { - return ProjectAdapter.this.projectList.get(oldItemPosition).id == - projectList.get(newItemPosition).id; - } - - @Override - public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { - Project project = projectList.get(newItemPosition); - Project old = projectList.get(oldItemPosition); - return project.id == old.id - && Objects.equals(project.git_url, old.git_url); - } - }); - this.projectList = projectList; - result.dispatchUpdatesTo(this); - } - } - - @Override - public ProjectViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - ProjectListItemBinding binding = DataBindingUtil - .inflate(LayoutInflater.from(parent.getContext()), R.layout.project_list_item, - parent, false); - - binding.setCallback(projectClickCallback); - - return new ProjectViewHolder(binding); - } - - @Override - public void onBindViewHolder(ProjectViewHolder holder, int position) { - holder.binding.setProject(projectList.get(position)); - holder.binding.executePendingBindings(); - } - - @Override - public int getItemCount() { - return projectList == null ? 0 : projectList.size(); - } - - static class ProjectViewHolder extends RecyclerView.ViewHolder { - - final ProjectListItemBinding binding; - - public ProjectViewHolder(ProjectListItemBinding binding) { - super(binding.getRoot()); - this.binding = binding; - } - } -} diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/view/adapter/ProjectAdapter.kt b/app/src/main/java/com/example/test/mvvmsampleapp/view/adapter/ProjectAdapter.kt new file mode 100755 index 0000000..17ee93c --- /dev/null +++ b/app/src/main/java/com/example/test/mvvmsampleapp/view/adapter/ProjectAdapter.kt @@ -0,0 +1,77 @@ +package com.example.test.mvvmsampleapp.view.adapter + +import android.databinding.DataBindingUtil +import android.support.v7.util.DiffUtil +import android.support.v7.widget.RecyclerView +import android.view.LayoutInflater +import android.view.ViewGroup +import com.example.test.mvvmsampleapp.R +import com.example.test.mvvmsampleapp.databinding.ProjectListItemBinding +import com.example.test.mvvmsampleapp.service.model.Project +import com.example.test.mvvmsampleapp.view.callback.ProjectClickCallback +import com.example.test.mvvmsampleapp.view.viewholders.ProjectViewHolder + +/** + * Adapter which shows a list of repositories. + */ +class ProjectAdapter(private val projectClickCallback: ProjectClickCallback?) + : RecyclerView.Adapter() { + + internal var projectList: List? = null + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProjectViewHolder { + val binding = DataBindingUtil + .inflate(LayoutInflater.from(parent.context), + R.layout.project_list_item, + parent, false) + + binding.callback = projectClickCallback + + return ProjectViewHolder(binding) + } + + override fun onBindViewHolder(holder: ProjectViewHolder, position: Int) { + holder.binding.project = projectList!![position] + holder.binding.executePendingBindings() + } + + override fun getItemCount(): Int { + return if (projectList == null) 0 else projectList!!.size + } + + fun setProjectList(projectList: List) { + if (this.projectList == null) { + this.projectList = projectList + notifyDataSetChanged() + } else { + calculateDiff(projectList) + } + } + + private fun calculateDiff(projectList: List) { + val result = DiffUtil.calculateDiff(object : DiffUtil.Callback() { + override fun getOldListSize(): Int { + return this@ProjectAdapter.projectList!!.size + } + + override fun getNewListSize(): Int { + return projectList.size + } + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + return this@ProjectAdapter.projectList!![oldItemPosition].id == + projectList[newItemPosition].id + } + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + val project = projectList[newItemPosition] + val old = projectList[oldItemPosition] + return project.id == old.id && + project.git_url == old.git_url + } + }) + this.projectList = projectList + result.dispatchUpdatesTo(this) + } + +} diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/view/callback/ProjectClickCallback.java b/app/src/main/java/com/example/test/mvvmsampleapp/view/callback/ProjectClickCallback.java deleted file mode 100755 index dcb8d4b..0000000 --- a/app/src/main/java/com/example/test/mvvmsampleapp/view/callback/ProjectClickCallback.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.example.test.mvvmsampleapp.view.callback; - -import com.example.test.mvvmsampleapp.service.model.Project; - -public interface ProjectClickCallback { - void onClick(Project project); -} diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/view/callback/ProjectClickCallback.kt b/app/src/main/java/com/example/test/mvvmsampleapp/view/callback/ProjectClickCallback.kt new file mode 100755 index 0000000..a3f3b1f --- /dev/null +++ b/app/src/main/java/com/example/test/mvvmsampleapp/view/callback/ProjectClickCallback.kt @@ -0,0 +1,7 @@ +package com.example.test.mvvmsampleapp.view.callback + +import com.example.test.mvvmsampleapp.service.model.Project + +interface ProjectClickCallback { + fun onClick(project: Project) +} diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/view/ui/MainActivity.java b/app/src/main/java/com/example/test/mvvmsampleapp/view/ui/MainActivity.java deleted file mode 100755 index 3c8e58b..0000000 --- a/app/src/main/java/com/example/test/mvvmsampleapp/view/ui/MainActivity.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.example.test.mvvmsampleapp.view.ui; - -import android.arch.lifecycle.LifecycleActivity; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; - -import com.example.test.mvvmsampleapp.R; -import com.example.test.mvvmsampleapp.service.model.Project; - -import javax.inject.Inject; - -import dagger.android.DispatchingAndroidInjector; -import dagger.android.support.HasSupportFragmentInjector; - -public class MainActivity extends LifecycleActivity implements HasSupportFragmentInjector { - - @Inject - DispatchingAndroidInjector dispatchingAndroidInjector; - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - - // Add project list fragment if this is first creation - if (savedInstanceState == null) { - ProjectListFragment fragment = new ProjectListFragment(); - - getSupportFragmentManager().beginTransaction() - .add(R.id.fragment_container, fragment, ProjectListFragment.TAG).commit(); - } - } - - /** Shows the project detail fragment */ - public void show(Project project) { - ProjectFragment projectFragment = ProjectFragment.forProject(project.name); - - getSupportFragmentManager() - .beginTransaction() - .addToBackStack("project") - .replace(R.id.fragment_container, - projectFragment, null).commit(); - } - - @Override - public DispatchingAndroidInjector supportFragmentInjector() { - return dispatchingAndroidInjector; - } -} diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/view/ui/MainActivity.kt b/app/src/main/java/com/example/test/mvvmsampleapp/view/ui/MainActivity.kt new file mode 100755 index 0000000..c57570e --- /dev/null +++ b/app/src/main/java/com/example/test/mvvmsampleapp/view/ui/MainActivity.kt @@ -0,0 +1,49 @@ +package com.example.test.mvvmsampleapp.view.ui + +import android.os.Bundle +import android.support.v4.app.Fragment +import android.support.v7.app.AppCompatActivity +import com.example.test.mvvmsampleapp.R +import com.example.test.mvvmsampleapp.service.model.Project +import dagger.android.DispatchingAndroidInjector +import dagger.android.support.HasSupportFragmentInjector +import javax.inject.Inject + +/** + * Main App activity which presents different fragments. + */ +class MainActivity : AppCompatActivity(), HasSupportFragmentInjector { + + @Inject + lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + // Add project list fragment if this is first creation + if (savedInstanceState == null) { + val fragment = ProjectListFragment() + + supportFragmentManager.beginTransaction() + .add(R.id.fragment_container, fragment, ProjectListFragment.TAG) + .commit() + } + } + + /** Shows the project detail fragment */ + fun show(project: Project?) { + val projectFragment = ProjectFragment.newInstance(project?.name) + + supportFragmentManager + .beginTransaction() + .addToBackStack("project") + .replace(R.id.fragment_container, + projectFragment, null).commit() + } + + override fun supportFragmentInjector(): DispatchingAndroidInjector? { + return dispatchingAndroidInjector + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/view/ui/ProjectFragment.java b/app/src/main/java/com/example/test/mvvmsampleapp/view/ui/ProjectFragment.java deleted file mode 100755 index e0d8c2f..0000000 --- a/app/src/main/java/com/example/test/mvvmsampleapp/view/ui/ProjectFragment.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.example.test.mvvmsampleapp.view.ui; - -import android.arch.lifecycle.LifecycleFragment; -import android.arch.lifecycle.Observer; -import android.arch.lifecycle.ViewModelProvider; -import android.arch.lifecycle.ViewModelProviders; -import android.databinding.DataBindingUtil; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.example.test.mvvmsampleapp.R; -import com.example.test.mvvmsampleapp.databinding.FragmentProjectDetailsBinding; -import com.example.test.mvvmsampleapp.di.Injectable; -import com.example.test.mvvmsampleapp.service.model.Project; -import com.example.test.mvvmsampleapp.viewmodel.ProjectViewModel; - -import javax.inject.Inject; - -public class ProjectFragment extends LifecycleFragment implements Injectable { - private static final String KEY_PROJECT_ID = "project_id"; - private FragmentProjectDetailsBinding binding; - - @Inject - ViewModelProvider.Factory viewModelFactory; - - @Nullable - @Override - public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - // Inflate this data binding layout - binding = DataBindingUtil.inflate(inflater, R.layout.fragment_project_details, container, false); - - // Create and set the adapter for the RecyclerView. - return binding.getRoot(); - } - - @Override - public void onActivityCreated(@Nullable Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - final ProjectViewModel viewModel = ViewModelProviders.of(this, viewModelFactory) - .get(ProjectViewModel.class); - - viewModel.setProjectID(getArguments().getString(KEY_PROJECT_ID)); - - binding.setProjectViewModel(viewModel); - binding.setIsLoading(true); - - observeViewModel(viewModel); - } - - private void observeViewModel(final ProjectViewModel viewModel) { - // Observe project data - viewModel.getObservableProject().observe(this, new Observer() { - @Override - public void onChanged(@Nullable Project project) { - if (project != null) { - binding.setIsLoading(false); - viewModel.setProject(project); - } - } - }); - } - - /** Creates project fragment for specific project ID */ - public static ProjectFragment forProject(String projectID) { - ProjectFragment fragment = new ProjectFragment(); - Bundle args = new Bundle(); - - args.putString(KEY_PROJECT_ID, projectID); - fragment.setArguments(args); - - return fragment; - } -} diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/view/ui/ProjectFragment.kt b/app/src/main/java/com/example/test/mvvmsampleapp/view/ui/ProjectFragment.kt new file mode 100755 index 0000000..b3078f3 --- /dev/null +++ b/app/src/main/java/com/example/test/mvvmsampleapp/view/ui/ProjectFragment.kt @@ -0,0 +1,76 @@ +package com.example.test.mvvmsampleapp.view.ui + +import android.arch.lifecycle.Observer +import android.arch.lifecycle.ViewModelProvider +import android.arch.lifecycle.ViewModelProviders +import android.databinding.DataBindingUtil +import android.os.Bundle +import android.support.v4.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.example.test.mvvmsampleapp.R +import com.example.test.mvvmsampleapp.databinding.FragmentProjectDetailsBinding +import com.example.test.mvvmsampleapp.di.Injectable +import com.example.test.mvvmsampleapp.viewmodel.ProjectViewModel +import javax.inject.Inject + +/** + * Fragment for a detailed info about a GitHub Repository. + */ +class ProjectFragment : Fragment(), Injectable { + + @Inject + lateinit var viewModelFactory: ViewModelProvider.Factory + + private lateinit var binding: FragmentProjectDetailsBinding + + companion object { + + private val KEY_PROJECT_ID = "project_id" + + /** Creates project fragment for specific project ID */ + fun newInstance(projectID: String?): ProjectFragment { + val fragment = ProjectFragment() + val args = Bundle() + + args.putString(KEY_PROJECT_ID, projectID) + fragment.arguments = args + + return fragment + } + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + // Inflate this data binding layout + binding = DataBindingUtil.inflate(inflater, R.layout.fragment_project_details, container, false) + + // Create and set the adapter for the RecyclerView. + return binding.root + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + + val viewModel = ViewModelProviders.of(this, viewModelFactory) + .get(ProjectViewModel::class.java) + + viewModel.setProjectID(arguments?.getString(KEY_PROJECT_ID)) + + binding.projectViewModel = viewModel + binding.isLoading = true + + observeViewModel(viewModel) + } + + private fun observeViewModel(viewModel: ProjectViewModel) { + // Observe project data + viewModel.observableProject.observe(this, Observer { project -> + if (project != null) { + binding.isLoading = false + viewModel.setProject(project) + } + }) + } +} diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/view/ui/ProjectListFragment.java b/app/src/main/java/com/example/test/mvvmsampleapp/view/ui/ProjectListFragment.java deleted file mode 100755 index 2ed18a4..0000000 --- a/app/src/main/java/com/example/test/mvvmsampleapp/view/ui/ProjectListFragment.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.example.test.mvvmsampleapp.view.ui; - -import android.arch.lifecycle.Lifecycle; -import android.arch.lifecycle.LifecycleFragment; -import android.arch.lifecycle.Observer; -import android.arch.lifecycle.ViewModelProvider; -import android.arch.lifecycle.ViewModelProviders; -import android.databinding.DataBindingUtil; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.example.test.mvvmsampleapp.R; -import com.example.test.mvvmsampleapp.databinding.FragmentProjectListBinding; -import com.example.test.mvvmsampleapp.di.Injectable; -import com.example.test.mvvmsampleapp.service.model.Project; -import com.example.test.mvvmsampleapp.view.adapter.ProjectAdapter; -import com.example.test.mvvmsampleapp.view.callback.ProjectClickCallback; -import com.example.test.mvvmsampleapp.viewmodel.ProjectListViewModel; - -import java.util.List; - -import javax.inject.Inject; - -public class ProjectListFragment extends LifecycleFragment implements Injectable { - public static final String TAG = "ProjectListFragment"; - private ProjectAdapter projectAdapter; - private FragmentProjectListBinding binding; - - @Inject - ViewModelProvider.Factory viewModelFactory; - - @Nullable - @Override - public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - binding = DataBindingUtil.inflate(inflater, R.layout.fragment_project_list, container, false); - - projectAdapter = new ProjectAdapter(projectClickCallback); - binding.projectList.setAdapter(projectAdapter); - binding.setIsLoading(true); - - return binding.getRoot(); - } - - @Override - public void onActivityCreated(@Nullable Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - final ProjectListViewModel viewModel = ViewModelProviders.of(this, - viewModelFactory).get(ProjectListViewModel.class); - - observeViewModel(viewModel); - } - - private void observeViewModel(ProjectListViewModel viewModel) { - // Update the list when the data changes - viewModel.getProjectListObservable().observe(this, new Observer>() { - @Override - public void onChanged(@Nullable List projects) { - if (projects != null) { - binding.setIsLoading(false); - projectAdapter.setProjectList(projects); - } - } - }); - } - - private final ProjectClickCallback projectClickCallback = new ProjectClickCallback() { - @Override - public void onClick(Project project) { - if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) { - ((MainActivity) getActivity()).show(project); - } - } - }; -} diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/view/ui/ProjectListFragment.kt b/app/src/main/java/com/example/test/mvvmsampleapp/view/ui/ProjectListFragment.kt new file mode 100755 index 0000000..78bbea8 --- /dev/null +++ b/app/src/main/java/com/example/test/mvvmsampleapp/view/ui/ProjectListFragment.kt @@ -0,0 +1,76 @@ +package com.example.test.mvvmsampleapp.view.ui + +import android.arch.lifecycle.Lifecycle +import android.arch.lifecycle.Observer +import android.arch.lifecycle.ViewModelProvider +import android.arch.lifecycle.ViewModelProviders +import android.databinding.DataBindingUtil +import android.os.Bundle +import android.support.v4.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup + +import com.example.test.mvvmsampleapp.R +import com.example.test.mvvmsampleapp.databinding.FragmentProjectListBinding +import com.example.test.mvvmsampleapp.di.Injectable +import com.example.test.mvvmsampleapp.service.model.Project +import com.example.test.mvvmsampleapp.view.adapter.ProjectAdapter +import com.example.test.mvvmsampleapp.view.callback.ProjectClickCallback +import com.example.test.mvvmsampleapp.viewmodel.ProjectListViewModel + +import javax.inject.Inject + +/** + * Fragment with a list of GitHub repositories. + */ +class ProjectListFragment : Fragment(), Injectable { + + @Inject + lateinit var viewModelFactory: ViewModelProvider.Factory + + private lateinit var projectAdapter: ProjectAdapter + private lateinit var binding: FragmentProjectListBinding + + companion object { + val TAG = "ProjectListFragment" + } + + private val projectClickCallback = object : ProjectClickCallback { + override fun onClick(project: Project) { + if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) { + (activity as MainActivity).show(project) + } + } + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + binding = DataBindingUtil.inflate(inflater, R.layout.fragment_project_list, container, false) + + projectAdapter = ProjectAdapter(projectClickCallback) + binding.projectList.adapter = projectAdapter + binding.isLoading = true + + return binding.root + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + + val viewModel = ViewModelProviders.of(this, viewModelFactory) + .get(ProjectListViewModel::class.java) + + observeViewModel(viewModel) + } + + private fun observeViewModel(viewModel: ProjectListViewModel) { + // Update the list when the data changes + viewModel.projectListObservable.observe(this, Observer { projects -> + if (projects != null) { + binding.isLoading = false + projectAdapter.setProjectList(projects) + } + }) + } +} diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/view/viewholders/ProjectViewHolder.kt b/app/src/main/java/com/example/test/mvvmsampleapp/view/viewholders/ProjectViewHolder.kt new file mode 100644 index 0000000..43ca98e --- /dev/null +++ b/app/src/main/java/com/example/test/mvvmsampleapp/view/viewholders/ProjectViewHolder.kt @@ -0,0 +1,6 @@ +package com.example.test.mvvmsampleapp.view.viewholders + +import android.support.v7.widget.RecyclerView +import com.example.test.mvvmsampleapp.databinding.ProjectListItemBinding + +class ProjectViewHolder(val binding: ProjectListItemBinding) : RecyclerView.ViewHolder(binding.root) \ No newline at end of file diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/viewmodel/ProjectListViewModel.java b/app/src/main/java/com/example/test/mvvmsampleapp/viewmodel/ProjectListViewModel.java deleted file mode 100755 index 6e73421..0000000 --- a/app/src/main/java/com/example/test/mvvmsampleapp/viewmodel/ProjectListViewModel.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.example.test.mvvmsampleapp.viewmodel; - -import android.app.Application; -import android.arch.lifecycle.AndroidViewModel; -import android.arch.lifecycle.LiveData; -import android.support.annotation.NonNull; - -import com.example.test.mvvmsampleapp.service.model.Project; -import com.example.test.mvvmsampleapp.service.repository.ProjectRepository; - -import java.util.List; - -import javax.inject.Inject; - -public class ProjectListViewModel extends AndroidViewModel { - private final LiveData> projectListObservable; - - @Inject - public ProjectListViewModel(@NonNull ProjectRepository projectRepository, @NonNull Application application) { - super(application); - - // If any transformation is needed, this can be simply done by Transformations class ... - projectListObservable = projectRepository.getProjectList("Google"); - } - - /** - * Expose the LiveData Projects query so the UI can observe it. - */ - public LiveData> getProjectListObservable() { - return projectListObservable; - } -} diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/viewmodel/ProjectListViewModel.kt b/app/src/main/java/com/example/test/mvvmsampleapp/viewmodel/ProjectListViewModel.kt new file mode 100755 index 0000000..47cff31 --- /dev/null +++ b/app/src/main/java/com/example/test/mvvmsampleapp/viewmodel/ProjectListViewModel.kt @@ -0,0 +1,22 @@ +package com.example.test.mvvmsampleapp.viewmodel + +import android.app.Application +import android.arch.lifecycle.AndroidViewModel +import android.arch.lifecycle.LiveData + +import com.example.test.mvvmsampleapp.service.model.Project +import com.example.test.mvvmsampleapp.service.repository.ProjectRepository + +import javax.inject.Inject + +class ProjectListViewModel +@Inject constructor(projectRepository: ProjectRepository, + application: Application) : AndroidViewModel(application) { + + /** + * Expose the LiveData Projects query so the UI can observe it. + */ + val projectListObservable: LiveData> = + projectRepository.getProjectList("Google") + +} diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/viewmodel/ProjectViewModel.java b/app/src/main/java/com/example/test/mvvmsampleapp/viewmodel/ProjectViewModel.java deleted file mode 100755 index 9f95d93..0000000 --- a/app/src/main/java/com/example/test/mvvmsampleapp/viewmodel/ProjectViewModel.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.example.test.mvvmsampleapp.viewmodel; - -import android.app.Application; -import android.arch.lifecycle.AndroidViewModel; -import android.arch.lifecycle.LiveData; -import android.arch.lifecycle.MutableLiveData; -import android.arch.lifecycle.Transformations; -import android.databinding.ObservableField; -import android.support.annotation.NonNull; -import android.util.Log; - -import com.example.test.mvvmsampleapp.service.model.Project; -import com.example.test.mvvmsampleapp.service.repository.ProjectRepository; - -import javax.inject.Inject; - -public class ProjectViewModel extends AndroidViewModel { - private static final String TAG = ProjectViewModel.class.getName(); - private static final MutableLiveData ABSENT = new MutableLiveData(); - { - //noinspection unchecked - ABSENT.setValue(null); - } - - private final LiveData projectObservable; - private final MutableLiveData projectID; - - public ObservableField project = new ObservableField<>(); - - @Inject - public ProjectViewModel(@NonNull ProjectRepository projectRepository, @NonNull Application application) { - super(application); - - this.projectID = new MutableLiveData<>(); - - projectObservable = Transformations.switchMap(projectID, input -> { - if (input.isEmpty()) { - Log.i(TAG, "ProjectViewModel projectID is absent!!!"); - return ABSENT; - } - - Log.i(TAG,"ProjectViewModel projectID is " + projectID.getValue()); - - return projectRepository.getProjectDetails("Google", projectID.getValue()); - }); - } - - public LiveData getObservableProject() { - return projectObservable; - } - - public void setProject(Project project) { - this.project.set(project); - } - - public void setProjectID(String projectID) { - this.projectID.setValue(projectID); - } -} diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/viewmodel/ProjectViewModel.kt b/app/src/main/java/com/example/test/mvvmsampleapp/viewmodel/ProjectViewModel.kt new file mode 100755 index 0000000..18bb114 --- /dev/null +++ b/app/src/main/java/com/example/test/mvvmsampleapp/viewmodel/ProjectViewModel.kt @@ -0,0 +1,55 @@ +package com.example.test.mvvmsampleapp.viewmodel + +import android.app.Application +import android.arch.lifecycle.AndroidViewModel +import android.arch.lifecycle.LiveData +import android.arch.lifecycle.MutableLiveData +import android.arch.lifecycle.Transformations +import android.databinding.ObservableField +import android.util.Log + +import com.example.test.mvvmsampleapp.service.model.Project +import com.example.test.mvvmsampleapp.service.repository.ProjectRepository + +import javax.inject.Inject + +class ProjectViewModel @Inject +constructor(projectRepository: ProjectRepository, + application: Application) : AndroidViewModel(application) { + + val observableProject: LiveData + private var projectID: MutableLiveData = MutableLiveData() + + var project = ObservableField() + + companion object { + + private val TAG = ProjectViewModel::class.java.name + private val ABSENT = MutableLiveData() + + init { + ABSENT.value = null + } + } + + init { + observableProject = Transformations.switchMap(projectID) { input -> + + if (input.isEmpty()) { + Log.i(TAG, "ProjectViewModel projectID is absent!!!") + return@switchMap ABSENT + } + + Log.i(TAG, "ProjectViewModel projectID is " + projectID.value) + projectRepository.getProjectDetails("Google", projectID.value) + } + } + + fun setProject(project: Project) { + this.project.set(project) + } + + fun setProjectID(projectID: String?) { + this.projectID.value = projectID + } +} diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/viewmodel/ProjectViewModelFactory.java b/app/src/main/java/com/example/test/mvvmsampleapp/viewmodel/ProjectViewModelFactory.java deleted file mode 100644 index bf19687..0000000 --- a/app/src/main/java/com/example/test/mvvmsampleapp/viewmodel/ProjectViewModelFactory.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.example.test.mvvmsampleapp.viewmodel; - -import android.arch.lifecycle.ViewModel; -import android.arch.lifecycle.ViewModelProvider; -import android.support.v4.util.ArrayMap; - -import com.example.test.mvvmsampleapp.di.ViewModelSubComponent; - -import java.util.Map; -import java.util.concurrent.Callable; - -import javax.inject.Inject; -import javax.inject.Singleton; - -@Singleton -public class ProjectViewModelFactory implements ViewModelProvider.Factory { - private final ArrayMap> creators; - - @Inject - public ProjectViewModelFactory(ViewModelSubComponent viewModelSubComponent) { - creators = new ArrayMap<>(); - - // View models cannot be injected directly because they won't be bound to the owner's view model scope. - creators.put(ProjectViewModel.class, () -> viewModelSubComponent.projectViewModel()); - creators.put(ProjectListViewModel.class, () -> viewModelSubComponent.projectListViewModel()); - } - - @Override - public T create(Class modelClass) { - Callable creator = creators.get(modelClass); - if (creator == null) { - for (Map.Entry> entry : creators.entrySet()) { - if (modelClass.isAssignableFrom(entry.getKey())) { - creator = entry.getValue(); - break; - } - } - } - if (creator == null) { - throw new IllegalArgumentException("Unknown model class " + modelClass); - } - try { - return (T) creator.call(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/test/mvvmsampleapp/viewmodel/ProjectViewModelFactory.kt b/app/src/main/java/com/example/test/mvvmsampleapp/viewmodel/ProjectViewModelFactory.kt new file mode 100644 index 0000000..755d6be --- /dev/null +++ b/app/src/main/java/com/example/test/mvvmsampleapp/viewmodel/ProjectViewModelFactory.kt @@ -0,0 +1,51 @@ +package com.example.test.mvvmsampleapp.viewmodel + +import android.arch.lifecycle.ViewModel +import android.arch.lifecycle.ViewModelProvider +import android.support.v4.util.ArrayMap + +import com.example.test.mvvmsampleapp.di.ViewModelSubComponent +import java.util.concurrent.Callable + +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ProjectViewModelFactory @Inject +constructor(viewModelSubComponent: ViewModelSubComponent) : ViewModelProvider.Factory { + + private val creators: ArrayMap, Callable> = ArrayMap() + + init { + // View models cannot be injected directly because they won't be bound to the owner's view model scope. + creators.put(ProjectViewModel::class.java, + Callable { viewModelSubComponent.projectViewModel() }) + creators.put(ProjectListViewModel::class.java, + Callable { viewModelSubComponent.projectListViewModel() }) + } + + override fun create(modelClass: Class): T { + + var creator: Callable? = creators[modelClass] + + if (creator == null) { + for ((key, value) in creators) { + if (modelClass.isAssignableFrom(key)) { + creator = value + break + } + } + } + + if (creator == null) { + throw IllegalArgumentException("Unknown model class " + modelClass) + } + + try { + return creator.call() as T + } catch (e: Exception) { + throw RuntimeException(e) + } + + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 5a79861..62fe1c2 100755 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,8 +1,8 @@ - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_project_list.xml b/app/src/main/res/layout/fragment_project_list.xml index 1edb399..4a09426 100755 --- a/app/src/main/res/layout/fragment_project_list.xml +++ b/app/src/main/res/layout/fragment_project_list.xml @@ -45,7 +45,7 @@ android:contentDescription="@string/project_list" android:layout_width="match_parent" android:layout_height="match_parent" - app:layoutManager="LinearLayoutManager"/> + app:layoutManager="android.support.v7.widget.LinearLayoutManager"/> diff --git a/app/src/test/java/com/example/test/mvvmsampleapp/ExampleUnitTest.java b/app/src/test/java/com/example/test/mvvmsampleapp/ExampleUnitTest.java deleted file mode 100644 index 7ef2e8e..0000000 --- a/app/src/test/java/com/example/test/mvvmsampleapp/ExampleUnitTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.example.test.mvvmsampleapp; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Example local unit test, which will execute on the development machine (host). - * - * @see Testing documentation - */ -public class ExampleUnitTest { - @Test - public void addition_isCorrect() throws Exception { - assertEquals(4, 2 + 2); - } -} \ No newline at end of file diff --git a/app/src/test/java/com/example/test/mvvmsampleapp/ExampleUnitTest.kt b/app/src/test/java/com/example/test/mvvmsampleapp/ExampleUnitTest.kt new file mode 100644 index 0000000..14b0243 --- /dev/null +++ b/app/src/test/java/com/example/test/mvvmsampleapp/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.example.test.mvvmsampleapp + +import org.junit.Assert.assertEquals +import org.junit.Test + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see [Testing documentation](http://d.android.com/tools/testing) + */ +class ExampleUnitTest { + @Test + @Throws(Exception::class) + fun addition_isCorrect() { + assertEquals(4, (2 + 2).toLong()) + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index fe62d25..170d2ee 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { + ext.kotlin_version = '1.2.10' repositories { maven { url 'https://maven.google.com' } @@ -9,9 +10,11 @@ buildscript { // You need to add the following repository to download the // new plugin. google() + mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:3.0.0-alpha5' + classpath 'com.android.tools.build:gradle:3.1.0-alpha08' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 195cbd3..bac89db 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon May 29 15:39:33 EDT 2017 +#Mon Dec 11 20:03:43 PST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-milestone-1-all.zip +distributionUrl=https://services.gradle.org/distributions/gradle-4.4.1-all.zip