├── .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 | 
6 |
7 | []( 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 |
19 |
20 |
27 |
28 |
29 |
30 |
36 |
37 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_movie.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
23 |
24 |
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AliAsadi/Android-MVVM-Architecture/e03d1bf5bc06a9371e4f1efb82e2f93c5a318312/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AliAsadi/Android-MVVM-Architecture/e03d1bf5bc06a9371e4f1efb82e2f93c5a318312/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AliAsadi/Android-MVVM-Architecture/e03d1bf5bc06a9371e4f1efb82e2f93c5a318312/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AliAsadi/Android-MVVM-Architecture/e03d1bf5bc06a9371e4f1efb82e2f93c5a318312/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AliAsadi/Android-MVVM-Architecture/e03d1bf5bc06a9371e4f1efb82e2f93c5a318312/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AliAsadi/Android-MVVM-Architecture/e03d1bf5bc06a9371e4f1efb82e2f93c5a318312/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AliAsadi/Android-MVVM-Architecture/e03d1bf5bc06a9371e4f1efb82e2f93c5a318312/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AliAsadi/Android-MVVM-Architecture/e03d1bf5bc06a9371e4f1efb82e2f93c5a318312/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AliAsadi/Android-MVVM-Architecture/e03d1bf5bc06a9371e4f1efb82e2f93c5a318312/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AliAsadi/Android-MVVM-Architecture/e03d1bf5bc06a9371e4f1efb82e2f93c5a318312/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | AndroidMvvm
3 | Load Movies
4 | empty
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | google()
6 | mavenCentral()
7 | }
8 |
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:4.1.3'
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | google()
17 | mavenCentral()
18 | maven { url 'https://jitpack.io' }
19 | }
20 | }
21 |
22 | task clean(type: Delete) {
23 | delete rootProject.buildDir
24 | }
25 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AliAsadi/Android-MVVM-Architecture/e03d1bf5bc06a9371e4f1efb82e2f93c5a318312/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Dec 10 16:17:28 IST 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------