├── .gitignore ├── .idea ├── caches │ └── build_file_checksums.ser ├── codeStyles │ └── Project.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── example │ │ └── test │ │ └── mvvmsampleapp │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── test │ │ │ └── mvvmsampleapp │ │ │ ├── MVVMApplication.java │ │ │ ├── di │ │ │ ├── AppComponent.java │ │ │ ├── AppInjector.java │ │ │ ├── AppModule.java │ │ │ ├── FragmentBuildersModule.java │ │ │ ├── Injectable.java │ │ │ ├── MainActivityModule.java │ │ │ └── ViewModelSubComponent.java │ │ │ ├── service │ │ │ ├── model │ │ │ │ ├── Project.java │ │ │ │ └── User.java │ │ │ └── repository │ │ │ │ ├── GitHubService.java │ │ │ │ └── ProjectRepository.java │ │ │ ├── view │ │ │ ├── adapter │ │ │ │ ├── CustomBindingAdapter.java │ │ │ │ └── ProjectAdapter.java │ │ │ ├── callback │ │ │ │ └── ProjectClickCallback.java │ │ │ └── ui │ │ │ │ ├── MainActivity.java │ │ │ │ ├── ProjectFragment.java │ │ │ │ └── ProjectListFragment.java │ │ │ └── viewmodel │ │ │ ├── ProjectListViewModel.java │ │ │ ├── ProjectViewModel.java │ │ │ └── ProjectViewModelFactory.java │ └── res │ │ ├── drawable │ │ └── github.png │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── fragment_project_details.xml │ │ ├── fragment_project_list.xml │ │ └── project_list_item.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── example │ └── test │ └── mvvmsampleapp │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── readme.md └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hazems/mvvm-sample-app/ce10c4c1658a96df6be42b9cf376ddaf57109127/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 33 | 34 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 84 | 96 | 97 | 98 | 99 | 100 | 101 | 103 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | project.ext { 2 | support = "1.0.0" 3 | constraintlayout = "2.0.0-alpha2" 4 | arch = "2.0.0" 5 | retrofit = "2.0.2" 6 | constraintLayout = "1.0.2" 7 | dagger_version = "2.15" 8 | } 9 | 10 | apply plugin: 'com.android.application' 11 | 12 | android { 13 | compileSdkVersion 28 14 | buildToolsVersion '28.0.2' 15 | 16 | defaultConfig { 17 | applicationId "com.example.test.mvvm_sample_app" 18 | minSdkVersion 19 19 | targetSdkVersion 28 20 | versionCode 1 21 | versionName "1.0" 22 | } 23 | 24 | buildTypes { 25 | release { 26 | minifyEnabled false 27 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 28 | } 29 | } 30 | 31 | dataBinding { 32 | enabled = true 33 | } 34 | 35 | compileOptions { 36 | targetCompatibility 1.8 37 | sourceCompatibility 1.8 38 | } 39 | } 40 | 41 | dependencies { 42 | implementation fileTree(dir: 'libs', include: ['*.jar']) 43 | 44 | implementation "com.google.android.material:material:$project.support" 45 | implementation "androidx.appcompat:appcompat:$project.support" 46 | implementation "androidx.cardview:cardview:$project.support" 47 | implementation "androidx.recyclerview:recyclerview:$project.support" 48 | implementation "androidx.constraintlayout:constraintlayout:$project.constraintlayout" 49 | implementation "androidx.legacy:legacy-support-v4:$project.support" 50 | 51 | // Arch 52 | annotationProcessor "androidx.lifecycle:lifecycle-compiler:$project.arch" 53 | implementation "androidx.lifecycle:lifecycle-runtime:$project.arch" 54 | implementation "androidx.lifecycle:lifecycle-extensions:$project.arch" 55 | 56 | // Retrofit 57 | implementation "com.squareup.retrofit2:retrofit:$project.retrofit" 58 | implementation "com.squareup.retrofit2:converter-gson:$project.retrofit" 59 | 60 | // Dagger 61 | annotationProcessor "com.google.dagger:dagger-android-processor:$dagger_version" 62 | annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version" 63 | implementation "com.google.dagger:dagger:$project.dagger_version" 64 | implementation "com.google.dagger:dagger-android:$project.dagger_version" 65 | implementation "com.google.dagger:dagger-android-support:$project.dagger_version" 66 | 67 | testImplementation 'junit:junit:4.12' 68 | } 69 | -------------------------------------------------------------------------------- /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/hazemsaleh/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 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/test/mvvmsampleapp/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.example.test.mvvmsampleapp; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.example.test.mvvm_sample_app", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/test/mvvmsampleapp/MVVMApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.test.mvvmsampleapp; 2 | 3 | import android.app.Activity; 4 | import android.app.Application; 5 | 6 | import com.example.test.mvvmsampleapp.di.AppInjector; 7 | 8 | import javax.inject.Inject; 9 | 10 | import dagger.android.DispatchingAndroidInjector; 11 | import dagger.android.HasActivityInjector; 12 | 13 | public class MVVMApplication extends Application implements HasActivityInjector { 14 | 15 | @Inject 16 | DispatchingAndroidInjector dispatchingAndroidInjector; 17 | 18 | @Override 19 | public void onCreate() { 20 | super.onCreate(); 21 | AppInjector.init(this); 22 | } 23 | 24 | @Override 25 | public DispatchingAndroidInjector activityInjector() { 26 | return dispatchingAndroidInjector; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/test/mvvmsampleapp/di/AppComponent.java: -------------------------------------------------------------------------------- 1 | package com.example.test.mvvmsampleapp.di; 2 | 3 | import com.example.test.mvvmsampleapp.MVVMApplication; 4 | import android.app.Application; 5 | import javax.inject.Singleton; 6 | 7 | import dagger.BindsInstance; 8 | import dagger.Component; 9 | import dagger.android.AndroidInjectionModule; 10 | 11 | @Singleton 12 | @Component(modules = { 13 | AndroidInjectionModule.class, 14 | AppModule.class, 15 | MainActivityModule.class 16 | }) 17 | public interface AppComponent { 18 | @Component.Builder 19 | interface Builder { 20 | @BindsInstance Builder application(Application application); 21 | AppComponent build(); 22 | } 23 | void inject(MVVMApplication mvvmApplication); 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/test/mvvmsampleapp/di/AppInjector.java: -------------------------------------------------------------------------------- 1 | package com.example.test.mvvmsampleapp.di; 2 | 3 | import android.app.Activity; 4 | import android.app.Application; 5 | import android.os.Bundle; 6 | 7 | import com.example.test.mvvmsampleapp.MVVMApplication; 8 | 9 | import androidx.fragment.app.Fragment; 10 | import androidx.fragment.app.FragmentActivity; 11 | import androidx.fragment.app.FragmentManager; 12 | import dagger.android.AndroidInjection; 13 | import dagger.android.support.AndroidSupportInjection; 14 | import dagger.android.support.HasSupportFragmentInjector; 15 | 16 | /** 17 | * AppInjector is a helper class to automatically inject fragments if they implement {@link Injectable}. 18 | */ 19 | public class AppInjector { 20 | private AppInjector() {} 21 | 22 | public static void init(MVVMApplication mvvmApplication) { 23 | DaggerAppComponent.builder().application(mvvmApplication) 24 | .build().inject(mvvmApplication); 25 | 26 | mvvmApplication 27 | .registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() { 28 | @Override 29 | public void onActivityCreated(Activity activity, Bundle savedInstanceState) { 30 | handleActivity(activity); 31 | } 32 | 33 | @Override 34 | public void onActivityStarted(Activity activity) { 35 | 36 | } 37 | 38 | @Override 39 | public void onActivityResumed(Activity activity) { 40 | 41 | } 42 | 43 | @Override 44 | public void onActivityPaused(Activity activity) { 45 | 46 | } 47 | 48 | @Override 49 | public void onActivityStopped(Activity activity) { 50 | 51 | } 52 | 53 | @Override 54 | public void onActivitySaveInstanceState(Activity activity, Bundle outState) { 55 | 56 | } 57 | 58 | @Override 59 | public void onActivityDestroyed(Activity activity) { 60 | 61 | } 62 | }); 63 | } 64 | 65 | private static void handleActivity(Activity activity) { 66 | if (activity instanceof HasSupportFragmentInjector) { 67 | AndroidInjection.inject(activity); 68 | } 69 | if (activity instanceof FragmentActivity) { 70 | ((FragmentActivity) activity).getSupportFragmentManager() 71 | .registerFragmentLifecycleCallbacks( 72 | new FragmentManager.FragmentLifecycleCallbacks() { 73 | @Override 74 | public void onFragmentCreated(FragmentManager fm, Fragment fragment, 75 | Bundle savedInstanceState) { 76 | if (fragment instanceof Injectable) { 77 | AndroidSupportInjection.inject(fragment); 78 | } 79 | } 80 | }, true); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/test/mvvmsampleapp/di/AppModule.java: -------------------------------------------------------------------------------- 1 | package com.example.test.mvvmsampleapp.di; 2 | 3 | import com.example.test.mvvmsampleapp.service.repository.GitHubService; 4 | import com.example.test.mvvmsampleapp.viewmodel.ProjectViewModelFactory; 5 | 6 | import javax.inject.Singleton; 7 | 8 | import androidx.lifecycle.ViewModelProvider; 9 | import dagger.Module; 10 | import dagger.Provides; 11 | import retrofit2.Retrofit; 12 | import retrofit2.converter.gson.GsonConverterFactory; 13 | 14 | @Module(subcomponents = ViewModelSubComponent.class) 15 | class AppModule { 16 | @Singleton @Provides 17 | GitHubService provideGithubService() { 18 | return new Retrofit.Builder() 19 | .baseUrl(GitHubService.HTTPS_API_GITHUB_URL) 20 | .addConverterFactory(GsonConverterFactory.create()) 21 | .build() 22 | .create(GitHubService.class); 23 | } 24 | 25 | @Singleton 26 | @Provides 27 | ViewModelProvider.Factory provideViewModelFactory( 28 | ViewModelSubComponent.Builder viewModelSubComponent) { 29 | 30 | return new ProjectViewModelFactory(viewModelSubComponent.build()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/test/mvvmsampleapp/di/FragmentBuildersModule.java: -------------------------------------------------------------------------------- 1 | package com.example.test.mvvmsampleapp.di; 2 | 3 | import com.example.test.mvvmsampleapp.view.ui.ProjectFragment; 4 | import com.example.test.mvvmsampleapp.view.ui.ProjectListFragment; 5 | 6 | import dagger.Module; 7 | import dagger.android.ContributesAndroidInjector; 8 | 9 | @Module 10 | public abstract class FragmentBuildersModule { 11 | @ContributesAndroidInjector 12 | abstract ProjectFragment contributeProjectFragment(); 13 | 14 | @ContributesAndroidInjector 15 | abstract ProjectListFragment contributeProjectListFragment(); 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/test/mvvmsampleapp/di/Injectable.java: -------------------------------------------------------------------------------- 1 | package com.example.test.mvvmsampleapp.di; 2 | 3 | /** 4 | * Marker interface for fragments. 5 | */ 6 | public interface Injectable { 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/test/mvvmsampleapp/di/MainActivityModule.java: -------------------------------------------------------------------------------- 1 | package com.example.test.mvvmsampleapp.di; 2 | 3 | import com.example.test.mvvmsampleapp.view.ui.MainActivity; 4 | 5 | import dagger.Module; 6 | import dagger.android.ContributesAndroidInjector; 7 | 8 | @Module 9 | public abstract class MainActivityModule { 10 | @ContributesAndroidInjector(modules = FragmentBuildersModule.class) 11 | abstract MainActivity contributeMainActivity(); 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/test/mvvmsampleapp/di/ViewModelSubComponent.java: -------------------------------------------------------------------------------- 1 | package com.example.test.mvvmsampleapp.di; 2 | 3 | import com.example.test.mvvmsampleapp.viewmodel.ProjectListViewModel; 4 | import com.example.test.mvvmsampleapp.viewmodel.ProjectViewModel; 5 | 6 | import dagger.Subcomponent; 7 | 8 | /** 9 | * A sub component to create ViewModels. It is called by the 10 | * {@link com.example.test.mvvmsampleapp.viewmodel.ProjectViewModelFactory}. 11 | */ 12 | @Subcomponent 13 | public interface ViewModelSubComponent { 14 | @Subcomponent.Builder 15 | interface Builder { 16 | ViewModelSubComponent build(); 17 | } 18 | 19 | ProjectListViewModel projectListViewModel(); 20 | ProjectViewModel projectViewModel(); 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/test/mvvmsampleapp/service/model/Project.java: -------------------------------------------------------------------------------- 1 | package com.example.test.mvvmsampleapp.service.model; 2 | 3 | import java.util.Date; 4 | 5 | public class Project { 6 | public long id; 7 | public String name; 8 | public String full_name; 9 | public User owner; 10 | public String html_url; 11 | public String description; 12 | public String url; 13 | public Date created_at; 14 | public Date updated_at; 15 | public Date pushed_at; 16 | public String git_url; 17 | public String ssh_url; 18 | public String clone_url; 19 | public String svn_url; 20 | public String homepage; 21 | public int stargazers_count; 22 | public int watchers_count; 23 | public String language; 24 | public boolean has_issues; 25 | public boolean has_downloads; 26 | public boolean has_wiki; 27 | public boolean has_pages; 28 | public int forks_count; 29 | public int open_issues_count; 30 | public int forks; 31 | public int open_issues; 32 | public int watchers; 33 | public String default_branch; 34 | 35 | public Project() { 36 | } 37 | 38 | public Project(String name) { 39 | this.name = name; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/test/mvvmsampleapp/service/model/User.java: -------------------------------------------------------------------------------- 1 | package com.example.test.mvvmsampleapp.service.model; 2 | 3 | import java.util.Date; 4 | 5 | public class User { 6 | public String login; 7 | public long id; 8 | public String avatar_url; 9 | public String gravatar_id; 10 | public String url; 11 | public String html_url; 12 | public String followers_url; 13 | public String following_url; 14 | public String gists_url; 15 | public String starred_url; 16 | public String subscriptions_url; 17 | public String organizations_url; 18 | public String repos_url; 19 | public String events_url; 20 | public String received_events_url; 21 | public String type; 22 | public String name; 23 | public String blog; 24 | public String location; 25 | public String email; 26 | public int public_repos; 27 | public int public_gists; 28 | public int followers; 29 | public int following; 30 | public Date created_at; 31 | public Date updated_at; 32 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/test/mvvmsampleapp/service/repository/GitHubService.java: -------------------------------------------------------------------------------- 1 | package com.example.test.mvvmsampleapp.service.repository; 2 | 3 | import com.example.test.mvvmsampleapp.service.model.Project; 4 | 5 | import java.util.List; 6 | 7 | import retrofit2.Call; 8 | import retrofit2.http.GET; 9 | import retrofit2.http.Path; 10 | 11 | public interface GitHubService { 12 | String HTTPS_API_GITHUB_URL = "https://api.github.com/"; 13 | 14 | @GET("users/{user}/repos") 15 | Call> getProjectList(@Path("user") String user); 16 | 17 | @GET("/repos/{user}/{reponame}") 18 | Call getProjectDetails(@Path("user") String user, @Path("reponame") String projectName); 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/test/mvvmsampleapp/service/repository/ProjectRepository.java: -------------------------------------------------------------------------------- 1 | package com.example.test.mvvmsampleapp.service.repository; 2 | 3 | import androidx.lifecycle.LiveData; 4 | import androidx.lifecycle.MutableLiveData; 5 | 6 | import com.example.test.mvvmsampleapp.service.model.Project; 7 | 8 | import java.util.List; 9 | 10 | import javax.inject.Inject; 11 | import javax.inject.Singleton; 12 | 13 | import retrofit2.Call; 14 | import retrofit2.Callback; 15 | import retrofit2.Response; 16 | 17 | @Singleton 18 | public class ProjectRepository { 19 | private GitHubService gitHubService; 20 | 21 | @Inject 22 | public ProjectRepository(GitHubService gitHubService) { 23 | this.gitHubService = gitHubService; 24 | } 25 | 26 | public LiveData> getProjectList(String userId) { 27 | final MutableLiveData> data = new MutableLiveData<>(); 28 | 29 | gitHubService.getProjectList(userId).enqueue(new Callback>() { 30 | @Override 31 | public void onResponse(Call> call, Response> response) { 32 | data.setValue(response.body()); 33 | } 34 | 35 | @Override 36 | public void onFailure(Call> call, Throwable t) { 37 | // TODO better error handling in part #2 ... 38 | data.setValue(null); 39 | } 40 | }); 41 | 42 | return data; 43 | } 44 | 45 | public LiveData getProjectDetails(String userID, String projectName) { 46 | final MutableLiveData data = new MutableLiveData<>(); 47 | 48 | gitHubService.getProjectDetails(userID, projectName).enqueue(new Callback() { 49 | @Override 50 | public void onResponse(Call call, Response response) { 51 | simulateDelay(); 52 | data.setValue(response.body()); 53 | } 54 | 55 | @Override 56 | public void onFailure(Call call, Throwable t) { 57 | // TODO better error handling in part #2 ... 58 | data.setValue(null); 59 | } 60 | }); 61 | 62 | return data; 63 | } 64 | 65 | private void simulateDelay() { 66 | try { 67 | Thread.sleep(10); 68 | } catch (InterruptedException e) { 69 | e.printStackTrace(); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/test/mvvmsampleapp/view/adapter/CustomBindingAdapter.java: -------------------------------------------------------------------------------- 1 | package com.example.test.mvvmsampleapp.view.adapter; 2 | 3 | import androidx.databinding.BindingAdapter; 4 | import android.view.View; 5 | 6 | public class CustomBindingAdapter { 7 | @BindingAdapter("visibleGone") 8 | public static void showHide(View view, boolean show) { 9 | view.setVisibility(show ? View.VISIBLE : View.GONE); 10 | } 11 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/test/mvvmsampleapp/view/adapter/ProjectAdapter.java: -------------------------------------------------------------------------------- 1 | package com.example.test.mvvmsampleapp.view.adapter; 2 | 3 | import androidx.databinding.DataBindingUtil; 4 | import androidx.annotation.Nullable; 5 | import androidx.recyclerview.widget.DiffUtil; 6 | import androidx.recyclerview.widget.RecyclerView; 7 | import android.view.LayoutInflater; 8 | import android.view.ViewGroup; 9 | 10 | import com.example.test.mvvmsampleapp.R; 11 | import com.example.test.mvvmsampleapp.databinding.ProjectListItemBinding; 12 | import com.example.test.mvvmsampleapp.service.model.Project; 13 | import com.example.test.mvvmsampleapp.view.callback.ProjectClickCallback; 14 | 15 | import java.util.List; 16 | import java.util.Objects; 17 | 18 | public class ProjectAdapter extends RecyclerView.Adapter { 19 | 20 | List projectList; 21 | 22 | @Nullable 23 | private final ProjectClickCallback projectClickCallback; 24 | 25 | public ProjectAdapter(@Nullable ProjectClickCallback projectClickCallback) { 26 | this.projectClickCallback = projectClickCallback; 27 | } 28 | 29 | public void setProjectList(final List projectList) { 30 | if (this.projectList == null) { 31 | this.projectList = projectList; 32 | notifyItemRangeInserted(0, projectList.size()); 33 | } else { 34 | DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() { 35 | @Override 36 | public int getOldListSize() { 37 | return ProjectAdapter.this.projectList.size(); 38 | } 39 | 40 | @Override 41 | public int getNewListSize() { 42 | return projectList.size(); 43 | } 44 | 45 | @Override 46 | public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { 47 | return ProjectAdapter.this.projectList.get(oldItemPosition).id == 48 | projectList.get(newItemPosition).id; 49 | } 50 | 51 | @Override 52 | public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { 53 | Project project = projectList.get(newItemPosition); 54 | Project old = projectList.get(oldItemPosition); 55 | return project.id == old.id 56 | && Objects.equals(project.git_url, old.git_url); 57 | } 58 | }); 59 | this.projectList = projectList; 60 | result.dispatchUpdatesTo(this); 61 | } 62 | } 63 | 64 | @Override 65 | public ProjectViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 66 | ProjectListItemBinding binding = DataBindingUtil 67 | .inflate(LayoutInflater.from(parent.getContext()), R.layout.project_list_item, 68 | parent, false); 69 | 70 | binding.setCallback(projectClickCallback); 71 | 72 | return new ProjectViewHolder(binding); 73 | } 74 | 75 | @Override 76 | public void onBindViewHolder(ProjectViewHolder holder, int position) { 77 | holder.binding.setProject(projectList.get(position)); 78 | holder.binding.executePendingBindings(); 79 | } 80 | 81 | @Override 82 | public int getItemCount() { 83 | return projectList == null ? 0 : projectList.size(); 84 | } 85 | 86 | static class ProjectViewHolder extends RecyclerView.ViewHolder { 87 | 88 | final ProjectListItemBinding binding; 89 | 90 | public ProjectViewHolder(ProjectListItemBinding binding) { 91 | super(binding.getRoot()); 92 | this.binding = binding; 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/test/mvvmsampleapp/view/callback/ProjectClickCallback.java: -------------------------------------------------------------------------------- 1 | package com.example.test.mvvmsampleapp.view.callback; 2 | 3 | import com.example.test.mvvmsampleapp.service.model.Project; 4 | 5 | public interface ProjectClickCallback { 6 | void onClick(Project project); 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/test/mvvmsampleapp/view/ui/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.test.mvvmsampleapp.view.ui; 2 | 3 | import android.os.Bundle; 4 | 5 | import com.example.test.mvvmsampleapp.R; 6 | import com.example.test.mvvmsampleapp.service.model.Project; 7 | 8 | import javax.inject.Inject; 9 | 10 | import androidx.annotation.Nullable; 11 | import androidx.appcompat.app.AppCompatActivity; 12 | import androidx.fragment.app.Fragment; 13 | import dagger.android.DispatchingAndroidInjector; 14 | import dagger.android.support.HasSupportFragmentInjector; 15 | 16 | public class MainActivity extends AppCompatActivity implements HasSupportFragmentInjector { 17 | 18 | @Inject 19 | DispatchingAndroidInjector dispatchingAndroidInjector; 20 | 21 | @Override 22 | protected void onCreate(@Nullable Bundle savedInstanceState) { 23 | super.onCreate(savedInstanceState); 24 | setContentView(R.layout.activity_main); 25 | 26 | // Add project list fragment if this is first creation 27 | if (savedInstanceState == null) { 28 | ProjectListFragment fragment = new ProjectListFragment(); 29 | 30 | getSupportFragmentManager().beginTransaction() 31 | .add(R.id.fragment_container, fragment, ProjectListFragment.TAG).commit(); 32 | } 33 | } 34 | 35 | /** Shows the project detail fragment */ 36 | public void show(Project project) { 37 | ProjectFragment projectFragment = ProjectFragment.forProject(project.name); 38 | 39 | getSupportFragmentManager() 40 | .beginTransaction() 41 | .addToBackStack("project") 42 | .replace(R.id.fragment_container, 43 | projectFragment, null).commit(); 44 | } 45 | 46 | @Override 47 | public DispatchingAndroidInjector supportFragmentInjector() { 48 | return dispatchingAndroidInjector; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/test/mvvmsampleapp/view/ui/ProjectFragment.java: -------------------------------------------------------------------------------- 1 | package com.example.test.mvvmsampleapp.view.ui; 2 | 3 | import android.os.Bundle; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | import com.example.test.mvvmsampleapp.R; 9 | import com.example.test.mvvmsampleapp.databinding.FragmentProjectDetailsBinding; 10 | import com.example.test.mvvmsampleapp.di.Injectable; 11 | import com.example.test.mvvmsampleapp.service.model.Project; 12 | import com.example.test.mvvmsampleapp.viewmodel.ProjectViewModel; 13 | 14 | import javax.inject.Inject; 15 | 16 | import androidx.annotation.Nullable; 17 | import androidx.databinding.DataBindingUtil; 18 | import androidx.fragment.app.Fragment; 19 | import androidx.lifecycle.Observer; 20 | import androidx.lifecycle.ViewModelProvider; 21 | import androidx.lifecycle.ViewModelProviders; 22 | 23 | 24 | public class ProjectFragment extends Fragment implements Injectable { 25 | private static final String KEY_PROJECT_ID = "project_id"; 26 | private FragmentProjectDetailsBinding binding; 27 | 28 | @Inject 29 | ViewModelProvider.Factory viewModelFactory; 30 | 31 | @Nullable 32 | @Override 33 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, 34 | @Nullable Bundle savedInstanceState) { 35 | // Inflate this data binding layout 36 | binding = DataBindingUtil.inflate(inflater, R.layout.fragment_project_details, container, false); 37 | 38 | // Create and set the adapter for the RecyclerView. 39 | return (View) binding.getRoot(); 40 | } 41 | 42 | @Override 43 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 44 | super.onActivityCreated(savedInstanceState); 45 | 46 | final ProjectViewModel viewModel = ViewModelProviders.of(this, viewModelFactory) 47 | .get(ProjectViewModel.class); 48 | 49 | viewModel.setProjectID(getArguments().getString(KEY_PROJECT_ID)); 50 | 51 | binding.setProjectViewModel(viewModel); 52 | binding.setIsLoading(true); 53 | 54 | observeViewModel(viewModel); 55 | } 56 | 57 | private void observeViewModel(final ProjectViewModel viewModel) { 58 | // Observe project data 59 | viewModel.getObservableProject().observe(this, new Observer() { 60 | @Override 61 | public void onChanged(@Nullable Project project) { 62 | if (project != null) { 63 | binding.setIsLoading(false); 64 | viewModel.setProject(project); 65 | } 66 | } 67 | }); 68 | } 69 | 70 | /** Creates project fragment for specific project ID */ 71 | public static ProjectFragment forProject(String projectID) { 72 | ProjectFragment fragment = new ProjectFragment(); 73 | Bundle args = new Bundle(); 74 | 75 | args.putString(KEY_PROJECT_ID, projectID); 76 | fragment.setArguments(args); 77 | 78 | return fragment; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/test/mvvmsampleapp/view/ui/ProjectListFragment.java: -------------------------------------------------------------------------------- 1 | package com.example.test.mvvmsampleapp.view.ui; 2 | 3 | import android.os.Bundle; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | import com.example.test.mvvmsampleapp.R; 9 | import com.example.test.mvvmsampleapp.databinding.FragmentProjectListBinding; 10 | import com.example.test.mvvmsampleapp.di.Injectable; 11 | import com.example.test.mvvmsampleapp.service.model.Project; 12 | import com.example.test.mvvmsampleapp.view.adapter.ProjectAdapter; 13 | import com.example.test.mvvmsampleapp.view.callback.ProjectClickCallback; 14 | import com.example.test.mvvmsampleapp.viewmodel.ProjectListViewModel; 15 | 16 | import java.util.List; 17 | 18 | import javax.inject.Inject; 19 | 20 | import androidx.annotation.Nullable; 21 | import androidx.databinding.DataBindingUtil; 22 | import androidx.fragment.app.Fragment; 23 | import androidx.lifecycle.Lifecycle; 24 | import androidx.lifecycle.Observer; 25 | import androidx.lifecycle.ViewModelProvider; 26 | import androidx.lifecycle.ViewModelProviders; 27 | 28 | public class ProjectListFragment extends Fragment implements Injectable { 29 | public static final String TAG = "ProjectListFragment"; 30 | private ProjectAdapter projectAdapter; 31 | private FragmentProjectListBinding binding; 32 | 33 | @Inject 34 | ViewModelProvider.Factory viewModelFactory; 35 | 36 | @Nullable 37 | @Override 38 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, 39 | @Nullable Bundle savedInstanceState) { 40 | binding = DataBindingUtil.inflate(inflater, R.layout.fragment_project_list, container, false); 41 | 42 | projectAdapter = new ProjectAdapter(projectClickCallback); 43 | binding.projectList.setAdapter(projectAdapter); 44 | binding.setIsLoading(true); 45 | 46 | return (View) binding.getRoot(); 47 | } 48 | 49 | @Override 50 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 51 | super.onActivityCreated(savedInstanceState); 52 | 53 | final ProjectListViewModel viewModel = ViewModelProviders.of(this, 54 | viewModelFactory).get(ProjectListViewModel.class); 55 | 56 | observeViewModel(viewModel); 57 | } 58 | 59 | private void observeViewModel(ProjectListViewModel viewModel) { 60 | // Update the list when the data changes 61 | viewModel.getProjectListObservable().observe(this, new Observer>() { 62 | @Override 63 | public void onChanged(@Nullable List projects) { 64 | if (projects != null) { 65 | binding.setIsLoading(false); 66 | projectAdapter.setProjectList(projects); 67 | } 68 | } 69 | }); 70 | } 71 | 72 | private final ProjectClickCallback projectClickCallback = new ProjectClickCallback() { 73 | @Override 74 | public void onClick(Project project) { 75 | if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) { 76 | ((MainActivity) getActivity()).show(project); 77 | } 78 | } 79 | }; 80 | } 81 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/test/mvvmsampleapp/viewmodel/ProjectListViewModel.java: -------------------------------------------------------------------------------- 1 | package com.example.test.mvvmsampleapp.viewmodel; 2 | 3 | import android.app.Application; 4 | import androidx.lifecycle.AndroidViewModel; 5 | import androidx.lifecycle.LiveData; 6 | import androidx.annotation.NonNull; 7 | 8 | import com.example.test.mvvmsampleapp.service.model.Project; 9 | import com.example.test.mvvmsampleapp.service.repository.ProjectRepository; 10 | 11 | import java.util.List; 12 | 13 | import javax.inject.Inject; 14 | 15 | public class ProjectListViewModel extends AndroidViewModel { 16 | private final LiveData> projectListObservable; 17 | 18 | @Inject 19 | public ProjectListViewModel(@NonNull ProjectRepository projectRepository, @NonNull Application application) { 20 | super(application); 21 | 22 | // If any transformation is needed, this can be simply done by Transformations class ... 23 | projectListObservable = projectRepository.getProjectList("Google"); 24 | } 25 | 26 | /** 27 | * Expose the LiveData Projects query so the UI can observe it. 28 | */ 29 | public LiveData> getProjectListObservable() { 30 | return projectListObservable; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/test/mvvmsampleapp/viewmodel/ProjectViewModel.java: -------------------------------------------------------------------------------- 1 | package com.example.test.mvvmsampleapp.viewmodel; 2 | 3 | import android.app.Application; 4 | import android.util.Log; 5 | 6 | import com.example.test.mvvmsampleapp.service.model.Project; 7 | import com.example.test.mvvmsampleapp.service.repository.ProjectRepository; 8 | 9 | import javax.inject.Inject; 10 | 11 | import androidx.annotation.NonNull; 12 | import androidx.databinding.ObservableField; 13 | import androidx.lifecycle.AndroidViewModel; 14 | import androidx.lifecycle.LiveData; 15 | import androidx.lifecycle.MutableLiveData; 16 | import androidx.lifecycle.Transformations; 17 | 18 | public class ProjectViewModel extends AndroidViewModel { 19 | private static final String TAG = ProjectViewModel.class.getName(); 20 | private static final MutableLiveData ABSENT = new MutableLiveData(); 21 | { 22 | //noinspection unchecked 23 | ABSENT.setValue(null); 24 | } 25 | 26 | private final LiveData projectObservable; 27 | private final MutableLiveData projectID; 28 | 29 | public ObservableField project = new ObservableField<>(); 30 | 31 | @Inject 32 | public ProjectViewModel(@NonNull ProjectRepository projectRepository, @NonNull Application application) { 33 | super(application); 34 | 35 | this.projectID = new MutableLiveData<>(); 36 | 37 | projectObservable = Transformations.switchMap(projectID, input -> { 38 | if (input.isEmpty()) { 39 | Log.i(TAG, "ProjectViewModel projectID is absent!!!"); 40 | return ABSENT; 41 | } 42 | 43 | Log.i(TAG,"ProjectViewModel projectID is " + projectID.getValue()); 44 | 45 | return projectRepository.getProjectDetails("Google", projectID.getValue()); 46 | }); 47 | } 48 | 49 | public LiveData getObservableProject() { 50 | return projectObservable; 51 | } 52 | 53 | public void setProject(Project project) { 54 | this.project.set(project); 55 | } 56 | 57 | public void setProjectID(String projectID) { 58 | this.projectID.setValue(projectID); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/test/mvvmsampleapp/viewmodel/ProjectViewModelFactory.java: -------------------------------------------------------------------------------- 1 | package com.example.test.mvvmsampleapp.viewmodel; 2 | 3 | import androidx.lifecycle.ViewModel; 4 | import androidx.lifecycle.ViewModelProvider; 5 | import androidx.collection.ArrayMap; 6 | 7 | import com.example.test.mvvmsampleapp.di.ViewModelSubComponent; 8 | 9 | import java.util.Map; 10 | import java.util.concurrent.Callable; 11 | 12 | import javax.inject.Inject; 13 | import javax.inject.Singleton; 14 | 15 | @Singleton 16 | public class ProjectViewModelFactory implements ViewModelProvider.Factory { 17 | private final ArrayMap> creators; 18 | 19 | @Inject 20 | public ProjectViewModelFactory(ViewModelSubComponent viewModelSubComponent) { 21 | creators = new ArrayMap<>(); 22 | 23 | // View models cannot be injected directly because they won't be bound to the owner's view model scope. 24 | creators.put(ProjectViewModel.class, () -> viewModelSubComponent.projectViewModel()); 25 | creators.put(ProjectListViewModel.class, () -> viewModelSubComponent.projectListViewModel()); 26 | } 27 | 28 | @Override 29 | public T create(Class modelClass) { 30 | Callable creator = creators.get(modelClass); 31 | if (creator == null) { 32 | for (Map.Entry> entry : creators.entrySet()) { 33 | if (modelClass.isAssignableFrom(entry.getKey())) { 34 | creator = entry.getValue(); 35 | break; 36 | } 37 | } 38 | } 39 | if (creator == null) { 40 | throw new IllegalArgumentException("Unknown model class " + modelClass); 41 | } 42 | try { 43 | return (T) creator.call(); 44 | } catch (Exception e) { 45 | throw new RuntimeException(e); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hazems/mvvm-sample-app/ce10c4c1658a96df6be42b9cf376ddaf57109127/app/src/main/res/drawable/github.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_project_details.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 23 | 24 | 34 | 35 | 40 | 41 | 51 | 52 | 59 | 60 | 61 | 68 | 69 | 76 | 77 | 84 | 85 | 86 | 93 | 94 | 101 | 102 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_project_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | 24 | 25 | 31 | 32 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /app/src/main/res/layout/project_list_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 18 | 19 | 26 | 27 | 35 | 36 | 43 | 44 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hazems/mvvm-sample-app/ce10c4c1658a96df6be42b9cf376ddaf57109127/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hazems/mvvm-sample-app/ce10c4c1658a96df6be42b9cf376ddaf57109127/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hazems/mvvm-sample-app/ce10c4c1658a96df6be42b9cf376ddaf57109127/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hazems/mvvm-sample-app/ce10c4c1658a96df6be42b9cf376ddaf57109127/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hazems/mvvm-sample-app/ce10c4c1658a96df6be42b9cf376ddaf57109127/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hazems/mvvm-sample-app/ce10c4c1658a96df6be42b9cf376ddaf57109127/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hazems/mvvm-sample-app/ce10c4c1658a96df6be42b9cf376ddaf57109127/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hazems/mvvm-sample-app/ce10c4c1658a96df6be42b9cf376ddaf57109127/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hazems/mvvm-sample-app/ce10c4c1658a96df6be42b9cf376ddaf57109127/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hazems/mvvm-sample-app/ce10c4c1658a96df6be42b9cf376ddaf57109127/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #3F51B5 5 | #303F9F 6 | #FF4081 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 16dp 3 | 8dp 4 | 16dp 5 | 16dp 6 | 25sp 7 | 20sp 8 | 16sp 9 | 150dp 10 | 125dp 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | MVVM Sample App 3 | Loading projects ... 4 | Project list 5 | Name of the project 6 | Google GitHub Projects 7 | Project List 8 | Languages: %s 9 | Watchers: %s 10 | Open Issues: %s 11 | Created At: %s 12 | Updated At: %s 13 | Clone URL: %s 14 | Project Language 15 | Project Watchers 16 | Project Description 17 | Open Issues 18 | Loading Project ... 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 15 | 16 |