├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── aliasadi │ │ └── mvvm │ │ ├── App.java │ │ ├── data │ │ ├── DataManager.java │ │ ├── api │ │ │ └── MovieApi.java │ │ ├── db │ │ │ ├── MovieDao.java │ │ │ └── MovieDatabase.java │ │ ├── domain │ │ │ └── Movie.java │ │ ├── mapper │ │ │ └── MovieMapper.java │ │ ├── model │ │ │ ├── MovieRemote.java │ │ │ └── MovieResponse.java │ │ ├── repository │ │ │ └── movie │ │ │ │ ├── MovieCacheDataSource.java │ │ │ │ ├── MovieDataSource.java │ │ │ │ ├── MovieLocalDataSource.java │ │ │ │ ├── MovieRemoteDataSource.java │ │ │ │ ├── MovieRepository.java │ │ │ │ └── MovieRepositoryImpl.java │ │ └── service │ │ │ └── MovieService.java │ │ ├── ui │ │ ├── base │ │ │ ├── BaseActivity.java │ │ │ └── BaseViewModel.java │ │ ├── details │ │ │ ├── DetailsActivity.java │ │ │ ├── DetailsViewModel.java │ │ │ └── DetailsViewModelFactory.java │ │ └── main │ │ │ ├── MainActivity.java │ │ │ ├── MainViewModel.java │ │ │ ├── MainViewModelFactory.java │ │ │ └── MovieAdapter.java │ │ └── utils │ │ └── DiskExecutor.java │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ └── ic_launcher_background.xml │ ├── layout │ ├── activity_details.xml │ ├── activity_main.xml │ └── item_movie.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.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 │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── 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/ 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### A basic sample android application to understand MVVM in a very simple way. 4 | 5 | ![](https://i.imgur.com/xpbqHRZ.png) 6 | 7 | [![Android Arsenal]( https://img.shields.io/badge/Android%20Arsenal-android--mvvm--sample--app-green.svg?style=flat )]( https://android-arsenal.com/details/1/7403 ) 8 | 9 | 10 | #### The app has following packages: 11 | 1. **data**: It contains all the data accessing and manipulating components. 12 | 2. **ui**: View classes along with their corresponding ViewModel. 13 | 4. **utils**: Utility classes. 14 | 15 | #### Using Jetpack Architecture Components 16 | * LiveData 17 | * ViewModel 18 | 19 | #### Contributing 20 | Just make pull request. You are in! 21 | 22 | ### License 23 | ``` 24 | Copyright (C) 2018 Ali Asadi 25 | Licensed under the Apache License, Version 2.0 (the "License"); 26 | you may not use this file except in compliance with the License. 27 | You may obtain a copy of the License at 28 | 29 | http://www.apache.org/licenses/LICENSE-2.0 30 | 31 | Unless required by applicable law or agreed to in writing, software 32 | distributed under the License is distributed on an "AS IS" BASIS, 33 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 34 | See the License for the specific language governing permissions and 35 | limitations under the License. 36 | ``` 37 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 27 5 | buildToolsVersion '27.0.3' 6 | 7 | defaultConfig { 8 | applicationId "com.aliasadi.mvvm" 9 | minSdkVersion 19 10 | targetSdkVersion 27 11 | versionCode 1 12 | 13 | versionName "1.0" 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | } 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | 23 | buildFeatures { 24 | viewBinding = true 25 | } 26 | } 27 | 28 | dependencies { 29 | 30 | implementation fileTree(dir: 'libs', include: ['*.jar']) 31 | 32 | //android support 33 | implementation 'com.android.support:appcompat-v7:27.0.2' 34 | implementation 'com.android.support:recyclerview-v7:27.0.2' 35 | implementation 'com.android.support.constraint:constraint-layout:1.0.2' 36 | 37 | //gson 38 | implementation 'com.google.code.gson:gson:2.8.2' 39 | 40 | //extensions 41 | implementation "android.arch.lifecycle:extensions:1.1.1" 42 | 43 | //retrofit 44 | implementation 'com.squareup.retrofit2:retrofit:2.3.0' 45 | implementation 'com.squareup.retrofit2:converter-gson:2.3.0' 46 | 47 | //glide 48 | implementation 'com.github.bumptech.glide:glide:4.6.1' 49 | annotationProcessor 'com.github.bumptech.glide:compiler:4.6.1' 50 | 51 | //room database 52 | implementation 'android.arch.persistence.room:runtime:1.0.0' 53 | annotationProcessor 'android.arch.persistence.room:compiler:1.0.0' 54 | 55 | //power preference 56 | implementation "com.github.AliAsadi:PowerPreference:2.1.0" 57 | 58 | testImplementation 'junit:junit:4.12' 59 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 60 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' 61 | 62 | } 63 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/aliasadi/mvvm/App.java: -------------------------------------------------------------------------------- 1 | package com.aliasadi.mvvm; 2 | 3 | import android.app.Application; 4 | 5 | 6 | /** 7 | * Created by Ali Asadi on 10/03/2018. 8 | */ 9 | public class App extends Application { 10 | 11 | private static App sInstance; 12 | 13 | @Override 14 | public void onCreate() { 15 | super.onCreate(); 16 | sInstance = this; 17 | } 18 | 19 | public static App getInstance() { 20 | return sInstance; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/aliasadi/mvvm/data/DataManager.java: -------------------------------------------------------------------------------- 1 | package com.aliasadi.mvvm.data; 2 | 3 | import com.aliasadi.mvvm.data.repository.movie.MovieRepository; 4 | import com.aliasadi.mvvm.data.repository.movie.MovieRepositoryImpl; 5 | import com.aliasadi.mvvm.data.repository.movie.MovieCacheDataSource; 6 | import com.aliasadi.mvvm.data.repository.movie.MovieLocalDataSource; 7 | import com.aliasadi.mvvm.data.db.MovieDao; 8 | import com.aliasadi.mvvm.data.db.MovieDatabase; 9 | import com.aliasadi.mvvm.data.repository.movie.MovieRemoteDataSource; 10 | import com.aliasadi.mvvm.data.api.MovieApi; 11 | import com.aliasadi.mvvm.data.service.MovieService; 12 | import com.preference.PowerPreference; 13 | import com.preference.Preference; 14 | 15 | /** 16 | * Created by Ali Asadi on 26/03/2018. 17 | */ 18 | 19 | public class DataManager { 20 | 21 | private static DataManager sInstance; 22 | 23 | private DataManager() { 24 | // This class is not publicly instantiable 25 | } 26 | 27 | public static synchronized DataManager getInstance() { 28 | if (sInstance == null) { 29 | sInstance = new DataManager(); 30 | } 31 | return sInstance; 32 | } 33 | 34 | public Preference getPrefs() { 35 | return PowerPreference.getDefaultFile(); 36 | } 37 | 38 | public MovieRepository getMovieRepository() { 39 | 40 | MovieApi movieApi = MovieService.getInstance().getMovieApi(); 41 | MovieRemoteDataSource movieRemote = MovieRemoteDataSource.getInstance(movieApi); 42 | 43 | MovieDao movieDao = MovieDatabase.getInstance().movieDao(); 44 | MovieLocalDataSource movieLocal = MovieLocalDataSource.getInstance(movieDao); 45 | 46 | MovieCacheDataSource movieCache = MovieCacheDataSource.getsInstance(); 47 | 48 | return MovieRepositoryImpl.getInstance(movieRemote,movieLocal,movieCache); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/com/aliasadi/mvvm/data/api/MovieApi.java: -------------------------------------------------------------------------------- 1 | package com.aliasadi.mvvm.data.api; 2 | 3 | import com.aliasadi.mvvm.data.model.MovieResponse; 4 | 5 | import retrofit2.Call; 6 | import retrofit2.http.GET; 7 | 8 | public interface MovieApi { 9 | @GET("/movies") 10 | Call getMovies(); 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/aliasadi/mvvm/data/db/MovieDao.java: -------------------------------------------------------------------------------- 1 | package com.aliasadi.mvvm.data.db; 2 | 3 | import android.arch.persistence.room.Dao; 4 | import android.arch.persistence.room.Insert; 5 | import android.arch.persistence.room.OnConflictStrategy; 6 | import android.arch.persistence.room.Query; 7 | 8 | import com.aliasadi.mvvm.data.domain.Movie; 9 | import com.aliasadi.mvvm.data.model.MovieRemote; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * Created by Ali Asadi on 30/01/2019. 15 | */ 16 | @Dao 17 | public interface MovieDao { 18 | 19 | /** 20 | * Select all movies from the movies table. 21 | * 22 | * @return all movies. 23 | */ 24 | @Query("SELECT * FROM movies") 25 | List getMovies(); 26 | 27 | /** 28 | * Insert all movies. 29 | */ 30 | @Insert(onConflict = OnConflictStrategy.REPLACE) 31 | void saveMovies(List movies); 32 | 33 | /** 34 | * Delete all movies. 35 | */ 36 | @Query("DELETE FROM movies") 37 | void deleteMovies(); 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/aliasadi/mvvm/data/db/MovieDatabase.java: -------------------------------------------------------------------------------- 1 | package com.aliasadi.mvvm.data.db; 2 | 3 | import android.arch.persistence.room.Database; 4 | import android.arch.persistence.room.Room; 5 | import android.arch.persistence.room.RoomDatabase; 6 | 7 | import com.aliasadi.mvvm.App; 8 | import com.aliasadi.mvvm.data.domain.Movie; 9 | import com.aliasadi.mvvm.data.model.MovieRemote; 10 | 11 | /** 12 | * Created by Ali Asadi on 30/01/2019. 13 | */ 14 | @Database(entities = {Movie.class}, version = 1, exportSchema = false) 15 | public abstract class MovieDatabase extends RoomDatabase { 16 | public abstract MovieDao movieDao(); 17 | 18 | private static MovieDatabase sInstance; 19 | 20 | public static MovieDatabase getInstance() { 21 | if (sInstance == null) { 22 | sInstance = Room.databaseBuilder(App.getInstance(), MovieDatabase.class, "Movie.db").build(); 23 | } 24 | return sInstance; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/aliasadi/mvvm/data/domain/Movie.java: -------------------------------------------------------------------------------- 1 | package com.aliasadi.mvvm.data.domain; 2 | 3 | import android.arch.persistence.room.ColumnInfo; 4 | import android.arch.persistence.room.Entity; 5 | import android.arch.persistence.room.PrimaryKey; 6 | import android.os.Parcel; 7 | import android.os.Parcelable; 8 | 9 | import com.google.gson.annotations.Expose; 10 | import com.google.gson.annotations.SerializedName; 11 | 12 | /** 13 | * Created by Ali Asadi on 10/03/2018. 14 | */ 15 | @Entity(tableName = "movies") 16 | public class Movie implements Parcelable { 17 | 18 | @PrimaryKey 19 | @ColumnInfo(name = "id") 20 | private int id; 21 | 22 | @Expose 23 | @SerializedName("description") 24 | @ColumnInfo(name = "description") 25 | private String description; 26 | 27 | @Expose 28 | @SerializedName("image") 29 | @ColumnInfo(name = "image") 30 | private String image; 31 | 32 | @Expose 33 | @SerializedName("title") 34 | @ColumnInfo(name = "title") 35 | private String title; 36 | 37 | public Movie(int id, String description, String image, String title) { 38 | this.id = id; 39 | this.description = description; 40 | this.image = image; 41 | this.title = title; 42 | } 43 | 44 | public int getId() { 45 | return id; 46 | } 47 | 48 | public void setId(int id) { 49 | this.id = id; 50 | } 51 | 52 | public String getDescription() { 53 | return description; 54 | } 55 | 56 | public void setDescription(String description) { 57 | this.description = description; 58 | } 59 | 60 | public String getImage() { 61 | return image; 62 | } 63 | 64 | public void setImage(String image) { 65 | this.image = image; 66 | } 67 | 68 | public String getTitle() { 69 | return title; 70 | } 71 | 72 | public void setTitle(String title) { 73 | this.title = title; 74 | } 75 | 76 | public static final Creator CREATOR = new Creator() { 77 | @Override 78 | public Movie createFromParcel(Parcel in) { 79 | return new Movie(in); 80 | } 81 | 82 | @Override 83 | public Movie[] newArray(int size) { 84 | return new Movie[size]; 85 | } 86 | }; 87 | 88 | protected Movie(Parcel in) { 89 | description = in.readString(); 90 | image = in.readString(); 91 | title = in.readString(); 92 | } 93 | 94 | @Override 95 | public int describeContents() { 96 | return 0; 97 | } 98 | 99 | @Override 100 | public void writeToParcel(Parcel parcel, int i) { 101 | parcel.writeString(description); 102 | parcel.writeString(image); 103 | parcel.writeString(title); 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /app/src/main/java/com/aliasadi/mvvm/data/mapper/MovieMapper.java: -------------------------------------------------------------------------------- 1 | package com.aliasadi.mvvm.data.mapper; 2 | 3 | import com.aliasadi.mvvm.data.domain.Movie; 4 | import com.aliasadi.mvvm.data.model.MovieRemote; 5 | 6 | /** 7 | * Created by Ali Asadi on 05/04/2021 8 | */ 9 | public class MovieMapper { 10 | 11 | public static Movie toDomain(MovieRemote movieRemote) { 12 | return new Movie(movieRemote.getId(), movieRemote.getDescription(), 13 | movieRemote.getImage(), movieRemote.getTitle()); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/aliasadi/mvvm/data/model/MovieRemote.java: -------------------------------------------------------------------------------- 1 | package com.aliasadi.mvvm.data.model; 2 | 3 | import android.arch.persistence.room.ColumnInfo; 4 | import android.arch.persistence.room.Entity; 5 | import android.arch.persistence.room.PrimaryKey; 6 | import android.os.Parcel; 7 | import android.os.Parcelable; 8 | 9 | import com.google.gson.annotations.Expose; 10 | import com.google.gson.annotations.SerializedName; 11 | 12 | /** 13 | * Created by Ali Asadi on 10/03/2018. 14 | */ 15 | public class MovieRemote { 16 | 17 | private int id; 18 | private String description; 19 | private String image; 20 | private String title; 21 | 22 | public int getId() { 23 | return id; 24 | } 25 | 26 | public String getDescription() { 27 | return description; 28 | } 29 | 30 | public String getImage() { 31 | return image; 32 | } 33 | 34 | public String getTitle() { 35 | return title; 36 | } 37 | 38 | public void setId(int id) { 39 | this.id = id; 40 | } 41 | 42 | public void setDescription(String description) { 43 | this.description = description; 44 | } 45 | 46 | public void setImage(String image) { 47 | this.image = image; 48 | } 49 | 50 | public void setTitle(String title) { 51 | this.title = title; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/com/aliasadi/mvvm/data/model/MovieResponse.java: -------------------------------------------------------------------------------- 1 | package com.aliasadi.mvvm.data.model; 2 | 3 | import com.google.gson.annotations.Expose; 4 | import com.google.gson.annotations.SerializedName; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Created by Ali Asadi on 24/03/2018. 10 | */ 11 | 12 | public class MovieResponse { 13 | 14 | @Expose 15 | @SerializedName("movies") 16 | private List movies; 17 | 18 | public List getMovies() { 19 | return movies; 20 | } 21 | 22 | public void setMovies(List movies) { 23 | this.movies = movies; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/aliasadi/mvvm/data/repository/movie/MovieCacheDataSource.java: -------------------------------------------------------------------------------- 1 | package com.aliasadi.mvvm.data.repository.movie; 2 | 3 | import android.util.SparseArray; 4 | 5 | import com.aliasadi.mvvm.data.domain.Movie; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | /** 11 | * Created by Ali Asadi on 30/01/2019. 12 | */ 13 | public class MovieCacheDataSource implements MovieDataSource.Local { 14 | 15 | private static MovieCacheDataSource sInstance; 16 | 17 | private final SparseArray cachedMovies = new SparseArray<>(); 18 | 19 | public static MovieCacheDataSource getsInstance() { 20 | if (sInstance == null) { 21 | sInstance = new MovieCacheDataSource(); 22 | } 23 | return sInstance; 24 | } 25 | 26 | @Override 27 | public void getMovies(MovieRepository.LoadMoviesCallback callback) { 28 | 29 | if (cachedMovies.size() > 0) { 30 | List movies = new ArrayList<>(); 31 | for (int i = 0; i < cachedMovies.size(); i++) { 32 | int key = cachedMovies.keyAt(i); 33 | movies.add(cachedMovies.get(key)); 34 | } 35 | 36 | callback.onMoviesLoaded(movies); 37 | 38 | } else { 39 | callback.onDataNotAvailable(); 40 | } 41 | 42 | } 43 | 44 | @Override 45 | public void saveMovies(List movies) { 46 | cachedMovies.clear(); 47 | for (Movie movie : movies) { 48 | cachedMovies.put(movie.getId(), movie); 49 | } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/aliasadi/mvvm/data/repository/movie/MovieDataSource.java: -------------------------------------------------------------------------------- 1 | package com.aliasadi.mvvm.data.repository.movie; 2 | 3 | import com.aliasadi.mvvm.data.domain.Movie; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Created by Ali Asadi on 30/01/2019. 9 | */ 10 | public interface MovieDataSource { 11 | 12 | interface Remote { 13 | void getMovies(MovieRepository.LoadMoviesCallback callback); 14 | } 15 | 16 | interface Local extends Remote { 17 | void saveMovies(List movies); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/aliasadi/mvvm/data/repository/movie/MovieLocalDataSource.java: -------------------------------------------------------------------------------- 1 | package com.aliasadi.mvvm.data.repository.movie; 2 | 3 | import com.aliasadi.mvvm.data.db.MovieDao; 4 | import com.aliasadi.mvvm.data.domain.Movie; 5 | import com.aliasadi.mvvm.utils.DiskExecutor; 6 | 7 | import java.util.List; 8 | import java.util.concurrent.Executor; 9 | 10 | /** 11 | * Created by Ali Asadi on 30/01/2019. 12 | */ 13 | public class MovieLocalDataSource implements MovieDataSource.Local { 14 | 15 | private final Executor executor; 16 | private final MovieDao movieDao; 17 | 18 | private static MovieLocalDataSource instance; 19 | 20 | private MovieLocalDataSource(Executor executor, MovieDao movieDao) { 21 | this.executor = executor; 22 | this.movieDao = movieDao; 23 | } 24 | 25 | public static MovieLocalDataSource getInstance(MovieDao movieDao) { 26 | if (instance == null) { 27 | instance = new MovieLocalDataSource(new DiskExecutor(), movieDao); 28 | } 29 | return instance; 30 | } 31 | 32 | @Override 33 | public void getMovies(final MovieRepository.LoadMoviesCallback callback) { 34 | Runnable runnable = new Runnable() { 35 | @Override 36 | public void run() { 37 | List movies = movieDao.getMovies(); 38 | if (!movies.isEmpty()) { 39 | callback.onMoviesLoaded(movies); 40 | } else { 41 | callback.onDataNotAvailable(); 42 | } 43 | } 44 | }; 45 | executor.execute(runnable); 46 | } 47 | 48 | @Override 49 | public void saveMovies(final List movies) { 50 | Runnable runnable = new Runnable() { 51 | @Override 52 | public void run() { 53 | movieDao.saveMovies(movies); 54 | } 55 | }; 56 | executor.execute(runnable); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/com/aliasadi/mvvm/data/repository/movie/MovieRemoteDataSource.java: -------------------------------------------------------------------------------- 1 | package com.aliasadi.mvvm.data.repository.movie; 2 | 3 | import com.aliasadi.mvvm.data.api.MovieApi; 4 | import com.aliasadi.mvvm.data.domain.Movie; 5 | import com.aliasadi.mvvm.data.mapper.MovieMapper; 6 | import com.aliasadi.mvvm.data.model.MovieRemote; 7 | import com.aliasadi.mvvm.data.model.MovieResponse; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | import retrofit2.Call; 13 | import retrofit2.Callback; 14 | import retrofit2.Response; 15 | 16 | public class MovieRemoteDataSource implements MovieDataSource.Remote { 17 | 18 | private static MovieRemoteDataSource instance; 19 | 20 | private final MovieApi movieApi; 21 | 22 | private MovieRemoteDataSource(MovieApi movieApi) { 23 | this.movieApi = movieApi; 24 | } 25 | 26 | public static MovieRemoteDataSource getInstance(MovieApi movieApi) { 27 | if (instance == null) { 28 | instance = new MovieRemoteDataSource(movieApi); 29 | } 30 | return instance; 31 | } 32 | 33 | @Override 34 | public void getMovies(final MovieRepository.LoadMoviesCallback callback) { 35 | movieApi.getMovies().enqueue(new Callback() { 36 | @Override 37 | public void onResponse(Call call, Response response) { 38 | List movies = response.body() != null ? response.body().getMovies() : null; 39 | if (movies != null && !movies.isEmpty()) { 40 | final List moviesDomain = new ArrayList<>(); 41 | for (MovieRemote movieRemote : movies) { 42 | moviesDomain.add(MovieMapper.toDomain(movieRemote)); 43 | } 44 | callback.onMoviesLoaded(moviesDomain); 45 | } else { 46 | callback.onDataNotAvailable(); 47 | } 48 | } 49 | 50 | @Override 51 | public void onFailure(Call call, Throwable t) { 52 | callback.onError(); 53 | } 54 | }); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/com/aliasadi/mvvm/data/repository/movie/MovieRepository.java: -------------------------------------------------------------------------------- 1 | package com.aliasadi.mvvm.data.repository.movie; 2 | 3 | import com.aliasadi.mvvm.data.domain.Movie; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Created by Ali Asadi on 05/04/2021 9 | */ 10 | public interface MovieRepository { 11 | 12 | interface LoadMoviesCallback { 13 | void onMoviesLoaded(List movies); 14 | void onDataNotAvailable(); 15 | void onError(); 16 | } 17 | 18 | void getMovies(LoadMoviesCallback callback); 19 | void saveMovies(List movies); 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/aliasadi/mvvm/data/repository/movie/MovieRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package com.aliasadi.mvvm.data.repository.movie; 2 | 3 | import com.aliasadi.mvvm.data.domain.Movie; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Created by Ali Asadi on 29/01/2019. 9 | */ 10 | public class MovieRepositoryImpl implements MovieRepository { 11 | 12 | private final MovieDataSource.Remote movieRemote; 13 | private final MovieDataSource.Local movieLocal; 14 | private final MovieDataSource.Local movieCache; 15 | 16 | private static MovieRepositoryImpl instance; 17 | 18 | private MovieRepositoryImpl(MovieRemoteDataSource movieRemote, 19 | MovieLocalDataSource movieLocal, 20 | MovieCacheDataSource movieCache) { 21 | 22 | this.movieRemote = movieRemote; 23 | this.movieLocal = movieLocal; 24 | this.movieCache = movieCache; 25 | } 26 | 27 | public static MovieRepositoryImpl getInstance(MovieRemoteDataSource movieRemote, 28 | MovieLocalDataSource movieLocal, 29 | MovieCacheDataSource movieCache) { 30 | if (instance == null) { 31 | instance = new MovieRepositoryImpl(movieRemote, movieLocal, movieCache); 32 | } 33 | return instance; 34 | } 35 | 36 | @Override 37 | public void getMovies(final MovieRepository.LoadMoviesCallback callback) { 38 | if (callback == null) return; 39 | 40 | movieCache.getMovies(new MovieRepository.LoadMoviesCallback() { 41 | @Override 42 | public void onMoviesLoaded(List movies) { 43 | callback.onMoviesLoaded(movies); 44 | } 45 | 46 | @Override 47 | public void onDataNotAvailable() { 48 | getMoviesFromLocalDataSource(callback); 49 | } 50 | 51 | @Override 52 | public void onError() { 53 | //not implemented in cache data source 54 | } 55 | }); 56 | 57 | } 58 | 59 | @Override 60 | public void saveMovies(List movies) { 61 | movieLocal.saveMovies(movies); 62 | } 63 | 64 | private void getMoviesFromLocalDataSource(final MovieRepository.LoadMoviesCallback callback) { 65 | movieLocal.getMovies(new MovieRepository.LoadMoviesCallback() { 66 | @Override 67 | public void onMoviesLoaded(List movies) { 68 | callback.onMoviesLoaded(movies); 69 | refreshCache(movies); 70 | } 71 | 72 | @Override 73 | public void onDataNotAvailable() { 74 | getMoviesFromRemoteDataSource(callback); 75 | } 76 | 77 | @Override 78 | public void onError() { 79 | //not implemented in local data source 80 | } 81 | }); 82 | } 83 | 84 | private void getMoviesFromRemoteDataSource(final MovieRepository.LoadMoviesCallback callback) { 85 | movieRemote.getMovies(new MovieRepository.LoadMoviesCallback() { 86 | @Override 87 | public void onMoviesLoaded(List movies) { 88 | callback.onMoviesLoaded(movies); 89 | saveMovies(movies); 90 | refreshCache(movies); 91 | } 92 | 93 | @Override 94 | public void onDataNotAvailable() { 95 | callback.onDataNotAvailable(); 96 | } 97 | 98 | @Override 99 | public void onError() { 100 | callback.onError(); 101 | } 102 | }); 103 | } 104 | 105 | private void refreshCache(List movies) { 106 | movieCache.saveMovies(movies); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /app/src/main/java/com/aliasadi/mvvm/data/service/MovieService.java: -------------------------------------------------------------------------------- 1 | package com.aliasadi.mvvm.data.service; 2 | 3 | import com.aliasadi.mvvm.data.api.MovieApi; 4 | 5 | import retrofit2.Retrofit; 6 | import retrofit2.converter.gson.GsonConverterFactory; 7 | 8 | /** 9 | * Created by Ali Asadi on 26/03/2018. 10 | */ 11 | public class MovieService { 12 | 13 | private static final String URL = "https://demo7812962.mockable.io/"; 14 | 15 | private final MovieApi movieApi; 16 | 17 | private static MovieService singleton; 18 | 19 | private MovieService() { 20 | Retrofit mRetrofit = new Retrofit.Builder() 21 | .addConverterFactory(GsonConverterFactory.create()) 22 | .baseUrl(URL).build(); 23 | 24 | movieApi = mRetrofit.create(MovieApi.class); 25 | } 26 | 27 | public static MovieService getInstance() { 28 | if (singleton == null) { 29 | synchronized (MovieService.class) { 30 | if (singleton == null) { 31 | singleton = new MovieService(); 32 | } 33 | } 34 | } 35 | return singleton; 36 | } 37 | 38 | public MovieApi getMovieApi() { 39 | return movieApi; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/aliasadi/mvvm/ui/base/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.aliasadi.mvvm.ui.base; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.NonNull; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.view.LayoutInflater; 7 | import android.viewbinding.ViewBinding; 8 | 9 | /** 10 | * Created by Ali Asadi on 07/01/2019. 11 | */ 12 | public abstract class BaseActivity extends AppCompatActivity { 13 | 14 | protected VM viewModel; 15 | protected BINDING binding; 16 | 17 | @NonNull 18 | protected abstract VM createViewModel(); 19 | 20 | @NonNull 21 | protected abstract BINDING createViewBinding(LayoutInflater layoutInflater); 22 | 23 | @Override 24 | protected void onCreate(final Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | binding = createViewBinding(LayoutInflater.from(this)); 27 | setContentView(binding.getRoot()); 28 | viewModel = createViewModel(); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/aliasadi/mvvm/ui/base/BaseViewModel.java: -------------------------------------------------------------------------------- 1 | package com.aliasadi.mvvm.ui.base; 2 | 3 | import android.arch.lifecycle.ViewModel; 4 | 5 | /** 6 | * Created by Ali Asadi on 07/01/2019. 7 | */ 8 | public abstract class BaseViewModel extends ViewModel { 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/aliasadi/mvvm/ui/details/DetailsActivity.java: -------------------------------------------------------------------------------- 1 | package com.aliasadi.mvvm.ui.details; 2 | 3 | import android.arch.lifecycle.Observer; 4 | import android.arch.lifecycle.ViewModelProviders; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.os.Bundle; 8 | import android.support.annotation.NonNull; 9 | import android.support.annotation.Nullable; 10 | import android.view.LayoutInflater; 11 | import com.aliasadi.mvvm.data.domain.Movie; 12 | import com.aliasadi.mvvm.databinding.ActivityDetailsBinding; 13 | import com.aliasadi.mvvm.ui.base.BaseActivity; 14 | import com.bumptech.glide.Glide; 15 | 16 | /** 17 | * Created by Ali Asadi on 12/03/2018. 18 | */ 19 | public class DetailsActivity extends BaseActivity { 20 | 21 | private static final String EXTRA_MOVIE = "EXTRA_MOVIE"; 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | viewModel.loadMovieData(); 27 | viewModel.getMovie().observe(this, new Observer() { 28 | @Override 29 | public void onChanged(@Nullable Movie movie) { 30 | binding.title.setText(movie.getTitle()); 31 | binding.desc.setText(movie.getDescription()); 32 | Glide.with(getApplicationContext()).load(movie.getImage()).into(binding.image); 33 | } 34 | }); 35 | } 36 | 37 | @NonNull 38 | @Override 39 | protected DetailsViewModel createViewModel() { 40 | Movie movie = getIntent().getParcelableExtra(EXTRA_MOVIE); 41 | DetailsViewModelFactory factory = new DetailsViewModelFactory(movie); 42 | return ViewModelProviders.of(this, factory).get(DetailsViewModel.class); 43 | } 44 | 45 | @NonNull 46 | @Override 47 | protected ActivityDetailsBinding createViewBinding(LayoutInflater layoutInflater) { 48 | return ActivityDetailsBinding.inflate(layoutInflater); 49 | } 50 | 51 | public static void start(Context context, Movie movie) { 52 | Intent starter = new Intent(context, DetailsActivity.class); 53 | starter.putExtra(EXTRA_MOVIE, movie); 54 | context.startActivity(starter); 55 | } 56 | } 57 | 58 | -------------------------------------------------------------------------------- /app/src/main/java/com/aliasadi/mvvm/ui/details/DetailsViewModel.java: -------------------------------------------------------------------------------- 1 | package com.aliasadi.mvvm.ui.details; 2 | 3 | import android.arch.lifecycle.MutableLiveData; 4 | 5 | import com.aliasadi.mvvm.data.domain.Movie; 6 | import com.aliasadi.mvvm.data.model.MovieRemote; 7 | import com.aliasadi.mvvm.ui.base.BaseViewModel; 8 | 9 | 10 | /** 11 | * Created by Ali Asadi on 12/03/2018. 12 | */ 13 | class DetailsViewModel extends BaseViewModel { 14 | 15 | private final MutableLiveData movieLiveData = new MutableLiveData<>(); 16 | private final Movie movie; 17 | 18 | DetailsViewModel(Movie movie) { 19 | this.movie = movie; 20 | } 21 | 22 | public void loadMovieData() { 23 | movieLiveData.postValue(movie); 24 | } 25 | 26 | MutableLiveData getMovie() { 27 | return movieLiveData; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/aliasadi/mvvm/ui/details/DetailsViewModelFactory.java: -------------------------------------------------------------------------------- 1 | package com.aliasadi.mvvm.ui.details; 2 | 3 | import android.arch.lifecycle.ViewModel; 4 | import android.arch.lifecycle.ViewModelProvider; 5 | import android.support.annotation.NonNull; 6 | 7 | import com.aliasadi.mvvm.data.domain.Movie; 8 | import com.aliasadi.mvvm.data.model.MovieRemote; 9 | 10 | 11 | /** 12 | * Created by Ali Asadi on 19/12/2018. 13 | */ 14 | public class DetailsViewModelFactory implements ViewModelProvider.Factory { 15 | 16 | private final Movie movie; 17 | 18 | public DetailsViewModelFactory(Movie movie) { 19 | this.movie = movie; 20 | } 21 | 22 | @NonNull 23 | @Override 24 | public T create(@NonNull Class modelClass) { 25 | if (modelClass.isAssignableFrom(DetailsViewModel.class)) { 26 | return (T) new DetailsViewModel(movie); 27 | } 28 | 29 | throw new IllegalArgumentException("Unknown ViewModel class"); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/aliasadi/mvvm/ui/main/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.aliasadi.mvvm.ui.main; 2 | 3 | import android.arch.lifecycle.Observer; 4 | import android.arch.lifecycle.ViewModelProviders; 5 | import android.os.Bundle; 6 | import android.support.annotation.NonNull; 7 | import android.support.annotation.Nullable; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.widget.Toast; 11 | import com.aliasadi.mvvm.data.DataManager; 12 | import com.aliasadi.mvvm.data.domain.Movie; 13 | import com.aliasadi.mvvm.data.repository.movie.MovieRepository; 14 | import com.aliasadi.mvvm.databinding.ActivityMainBinding; 15 | import com.aliasadi.mvvm.ui.base.BaseActivity; 16 | import com.aliasadi.mvvm.ui.details.DetailsActivity; 17 | 18 | import java.util.List; 19 | 20 | /** 21 | * Created by Ali Asadi on 12/03/2018. 22 | */ 23 | 24 | public class MainActivity extends BaseActivity implements MovieAdapter.MovieListener { 25 | 26 | private MovieAdapter movieAdapter; 27 | 28 | @NonNull 29 | @Override 30 | protected MainViewModel createViewModel() { 31 | MovieRepository movieRepository = DataManager.getInstance().getMovieRepository(); 32 | MainViewModelFactory factory = new MainViewModelFactory(movieRepository); 33 | return ViewModelProviders.of(this, factory).get(MainViewModel.class); 34 | } 35 | 36 | @NonNull 37 | @Override 38 | protected ActivityMainBinding createViewBinding(LayoutInflater layoutInflater) { 39 | return ActivityMainBinding.inflate(layoutInflater); 40 | } 41 | 42 | @Override 43 | protected void onCreate(Bundle savedInstanceState) { 44 | super.onCreate(savedInstanceState); 45 | movieAdapter = new MovieAdapter(this); 46 | binding.recyclerView.setAdapter(movieAdapter); 47 | 48 | setListeners(); 49 | observeViewModel(); 50 | } 51 | 52 | private void setListeners() { 53 | binding.load.setOnClickListener(new View.OnClickListener() { 54 | @Override 55 | public void onClick(View view) { 56 | viewModel.loadMovies(); 57 | } 58 | }); 59 | 60 | binding.empty.setOnClickListener(new View.OnClickListener() { 61 | @Override 62 | public void onClick(View view) { 63 | viewModel.onEmptyClicked(); 64 | } 65 | }); 66 | } 67 | 68 | private void observeViewModel() { 69 | viewModel.getShowLoadingLiveData().observe(this, new Observer() { 70 | @Override 71 | public void onChanged(@Nullable Void aVoid) { 72 | binding.progressBar.setVisibility(View.VISIBLE); 73 | } 74 | }); 75 | 76 | viewModel.getHideLoadingLiveData().observe(this, new Observer() { 77 | @Override 78 | public void onChanged(@Nullable Void aVoid) { 79 | binding.progressBar.setVisibility(View.GONE); 80 | } 81 | }); 82 | 83 | viewModel.getMoviesLiveData().observe(this, new Observer>() { 84 | @Override 85 | public void onChanged(@Nullable List movies) { 86 | movieAdapter.setItems(movies); 87 | } 88 | }); 89 | 90 | viewModel.getNavigateToDetailsLiveData().observe(this, new Observer() { 91 | @Override 92 | public void onChanged(@Nullable Movie movie) { 93 | DetailsActivity.start(MainActivity.this, movie); 94 | } 95 | }); 96 | 97 | viewModel.getShowErrorMessageLiveData().observe(this, new Observer() { 98 | @Override 99 | public void onChanged(@Nullable String message) { 100 | Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show(); 101 | } 102 | }); 103 | } 104 | 105 | @Override 106 | public void onMovieClicked(Movie movie) { 107 | viewModel.onMovieClicked(movie); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /app/src/main/java/com/aliasadi/mvvm/ui/main/MainViewModel.java: -------------------------------------------------------------------------------- 1 | package com.aliasadi.mvvm.ui.main; 2 | 3 | import android.arch.lifecycle.LiveData; 4 | import android.arch.lifecycle.MutableLiveData; 5 | 6 | import com.aliasadi.mvvm.data.domain.Movie; 7 | import com.aliasadi.mvvm.data.model.MovieRemote; 8 | import com.aliasadi.mvvm.data.repository.movie.MovieRepository; 9 | import com.aliasadi.mvvm.ui.base.BaseViewModel; 10 | 11 | import java.util.Collections; 12 | import java.util.List; 13 | 14 | /** 15 | * Created by Ali Asadi on 18/12/2018. 16 | */ 17 | public class MainViewModel extends BaseViewModel { 18 | 19 | private final MutableLiveData> moviesLiveData = new MutableLiveData<>(); 20 | private final MutableLiveData showErrorMessageLiveData = new MutableLiveData<>(); 21 | private final MutableLiveData showLoadingLiveData = new MutableLiveData<>(); 22 | private final MutableLiveData hideLoadingLiveData = new MutableLiveData<>(); 23 | private final MutableLiveData navigateToDetailsLiveData = new MutableLiveData<>(); 24 | 25 | private final MovieRepository moviesRepository; 26 | 27 | private final MovieCallback movieCallback = new MovieCallback(); 28 | 29 | MainViewModel(MovieRepository moviesRepository) { 30 | this.moviesRepository = moviesRepository; 31 | } 32 | 33 | public void loadMovies() { 34 | setIsLoading(true); 35 | moviesRepository.getMovies(movieCallback); 36 | } 37 | 38 | public void onEmptyClicked() { 39 | setMoviesLiveData(Collections.emptyList()); 40 | } 41 | 42 | private void setIsLoading(boolean loading) { 43 | if (loading) { 44 | showLoadingLiveData.postValue(null); 45 | } else { 46 | hideLoadingLiveData.postValue(null); 47 | } 48 | } 49 | 50 | private void setMoviesLiveData(List moviesLiveData) { 51 | setIsLoading(false); 52 | this.moviesLiveData.postValue(moviesLiveData); 53 | } 54 | 55 | public void onMovieClicked(Movie movie) { 56 | navigateToDetailsLiveData.postValue(movie); 57 | } 58 | 59 | /** 60 | * Callback 61 | **/ 62 | private class MovieCallback implements MovieRepository.LoadMoviesCallback { 63 | @Override 64 | public void onMoviesLoaded(List movies) { 65 | setMoviesLiveData(movies); 66 | } 67 | 68 | @Override 69 | public void onDataNotAvailable() { 70 | setIsLoading(false); 71 | showErrorMessageLiveData.postValue("There is not items!"); 72 | } 73 | 74 | @Override 75 | public void onError() { 76 | setIsLoading(false); 77 | showErrorMessageLiveData.postValue("Something Went Wrong!"); 78 | } 79 | } 80 | 81 | /** 82 | * LiveData 83 | **/ 84 | public LiveData> getMoviesLiveData() { 85 | return moviesLiveData; 86 | } 87 | 88 | public LiveData getShowLoadingLiveData() { 89 | return showLoadingLiveData; 90 | } 91 | 92 | public LiveData getShowErrorMessageLiveData() { 93 | return showErrorMessageLiveData; 94 | } 95 | 96 | public LiveData getHideLoadingLiveData() { 97 | return hideLoadingLiveData; 98 | } 99 | 100 | public LiveData getNavigateToDetailsLiveData() { 101 | return navigateToDetailsLiveData; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /app/src/main/java/com/aliasadi/mvvm/ui/main/MainViewModelFactory.java: -------------------------------------------------------------------------------- 1 | package com.aliasadi.mvvm.ui.main; 2 | 3 | import android.arch.lifecycle.ViewModel; 4 | import android.arch.lifecycle.ViewModelProvider; 5 | import android.support.annotation.NonNull; 6 | 7 | import com.aliasadi.mvvm.data.repository.movie.MovieRepository; 8 | import com.aliasadi.mvvm.data.repository.movie.MovieRepositoryImpl; 9 | 10 | /** 11 | * Created by Ali Asadi on 19/12/2018. 12 | */ 13 | public class MainViewModelFactory implements ViewModelProvider.Factory { 14 | 15 | private final MovieRepository moviesRepository; 16 | 17 | public MainViewModelFactory(MovieRepository moviesRepository) { 18 | this.moviesRepository = moviesRepository; 19 | } 20 | 21 | @NonNull 22 | @Override 23 | public T create(@NonNull Class modelClass) { 24 | if (modelClass.isAssignableFrom(MainViewModel.class)) { 25 | return (T) new MainViewModel(moviesRepository); 26 | } 27 | 28 | throw new IllegalArgumentException("Unknown ViewModel class"); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/aliasadi/mvvm/ui/main/MovieAdapter.java: -------------------------------------------------------------------------------- 1 | package com.aliasadi.mvvm.ui.main; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import com.aliasadi.mvvm.data.domain.Movie; 8 | import com.aliasadi.mvvm.databinding.ItemMovieBinding; 9 | import com.bumptech.glide.Glide; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | /** 14 | * Created by Ali Asadi on 24/03/2018. 15 | */ 16 | public class MovieAdapter extends RecyclerView.Adapter { 17 | 18 | public interface MovieListener { 19 | void onMovieClicked(Movie movie); 20 | } 21 | 22 | private List items; 23 | private final MovieListener listener; 24 | 25 | public MovieAdapter(MovieListener listener) { 26 | this.listener = listener; 27 | items = new ArrayList<>(); 28 | } 29 | 30 | public void setItems(List items) { 31 | this.items = items; 32 | notifyDataSetChanged(); 33 | } 34 | 35 | @Override 36 | public MovieViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 37 | ItemMovieBinding binding = ItemMovieBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false); 38 | return new MovieViewHolder(binding); 39 | } 40 | 41 | @Override 42 | public void onBindViewHolder(MovieViewHolder holder, int position) { 43 | holder.bind(position); 44 | } 45 | 46 | @Override 47 | public int getItemCount() { 48 | return items.size(); 49 | } 50 | 51 | private Movie getItem(int position) { 52 | return items.get(position); 53 | } 54 | 55 | public class MovieViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { 56 | 57 | ItemMovieBinding binding; 58 | MovieViewHolder(ItemMovieBinding binding) { 59 | super(binding.getRoot()); 60 | this.binding = binding; 61 | } 62 | 63 | void bind(int position) { 64 | Movie movie = getItem(position); 65 | 66 | setClickListener(movie); 67 | setTitle(movie.getTitle()); 68 | setImage(movie.getImage()); 69 | setDescription(movie.getDescription()); 70 | } 71 | 72 | private void setTitle(String title) { 73 | binding.title.setText(title); 74 | } 75 | 76 | private void setImage(String imageUrl) { 77 | Glide.with(itemView.getContext()).load(imageUrl).into(binding.image); 78 | } 79 | 80 | private void setDescription(String description) { 81 | binding.desc.setText(description); 82 | } 83 | 84 | private void setClickListener(Movie movie) { 85 | itemView.setTag(movie); 86 | itemView.setOnClickListener(this); 87 | } 88 | 89 | @Override 90 | public void onClick(View view) { 91 | listener.onMovieClicked((Movie) view.getTag()); 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /app/src/main/java/com/aliasadi/mvvm/utils/DiskExecutor.java: -------------------------------------------------------------------------------- 1 | package com.aliasadi.mvvm.utils; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import java.util.concurrent.Executor; 6 | import java.util.concurrent.Executors; 7 | 8 | /** 9 | * Created by Ali Asadi on 31/07/2019. 10 | */ 11 | public class DiskExecutor implements Executor { 12 | 13 | private final Executor diskExecutor; 14 | 15 | public DiskExecutor() { 16 | this.diskExecutor = Executors.newSingleThreadExecutor(); 17 | } 18 | 19 | @Override 20 | public void execute(@NonNull Runnable runnable) { 21 | diskExecutor.execute(runnable); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_details.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 19 | 20 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 |