Repository: probelalkhan/android-mvvm-architecture Branch: master Commit: 14a19cf4771d Files: 71 Total size: 81.0 KB Directory structure: gitextract_u139hf2o/ ├── .gitignore ├── .idea/ │ ├── codeStyles/ │ │ ├── Project.xml │ │ └── codeStyleConfig.xml │ ├── encodings.xml │ ├── gradle.xml │ ├── misc.xml │ ├── runConfigurations.xml │ └── vcs.xml ├── app/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── net/ │ │ └── simplifiedcoding/ │ │ └── mvvmsampleapp/ │ │ └── ExampleInstrumentedTest.kt │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── net/ │ │ │ └── simplifiedcoding/ │ │ │ └── mvvmsampleapp/ │ │ │ ├── MVVMApplication.kt │ │ │ ├── data/ │ │ │ │ ├── db/ │ │ │ │ │ ├── AppDatabase.kt │ │ │ │ │ ├── QuoteDao.kt │ │ │ │ │ ├── UserDao.kt │ │ │ │ │ └── entities/ │ │ │ │ │ ├── Quote.kt │ │ │ │ │ └── User.kt │ │ │ │ ├── network/ │ │ │ │ │ ├── MyApi.kt │ │ │ │ │ ├── NetworkConnectionInterceptor.kt │ │ │ │ │ ├── SafeApiRequest.kt │ │ │ │ │ └── responses/ │ │ │ │ │ ├── AuthResponse.kt │ │ │ │ │ └── QuotesResponse.kt │ │ │ │ ├── preferences/ │ │ │ │ │ └── PreferenceProvider.kt │ │ │ │ └── repositories/ │ │ │ │ ├── QuotesRepository.kt │ │ │ │ └── UserRepository.kt │ │ │ ├── ui/ │ │ │ │ ├── auth/ │ │ │ │ │ ├── AuthViewModel.kt │ │ │ │ │ ├── AuthViewModelFactory.kt │ │ │ │ │ ├── LoginActivity.kt │ │ │ │ │ └── SignupActivity.kt │ │ │ │ └── home/ │ │ │ │ ├── HomeActivity.kt │ │ │ │ ├── profile/ │ │ │ │ │ ├── ProfileFragment.kt │ │ │ │ │ ├── ProfileViewModel.kt │ │ │ │ │ └── ProfileViewModelFactory.kt │ │ │ │ └── quotes/ │ │ │ │ ├── QuoteItem.kt │ │ │ │ ├── QuotesFragment.kt │ │ │ │ ├── QuotesViewModel.kt │ │ │ │ └── QuotesViewModelFactory.kt │ │ │ └── util/ │ │ │ ├── Coroutines.kt │ │ │ ├── Delegates.kt │ │ │ ├── Exceptions.kt │ │ │ └── ViewUtils.kt │ │ └── res/ │ │ ├── drawable/ │ │ │ ├── edit_text_round_gray_background.xml │ │ │ ├── ic_app_logo.xml │ │ │ ├── ic_email.xml │ │ │ ├── ic_launcher_background.xml │ │ │ ├── ic_lock.xml │ │ │ └── ic_name.xml │ │ ├── drawable-v24/ │ │ │ └── ic_launcher_foreground.xml │ │ ├── layout/ │ │ │ ├── activity_home.xml │ │ │ ├── activity_login.xml │ │ │ ├── activity_signup.xml │ │ │ ├── item_quote.xml │ │ │ ├── profile_fragment.xml │ │ │ └── quotes_fragment.xml │ │ ├── menu/ │ │ │ └── nav_menu.xml │ │ ├── mipmap-anydpi-v26/ │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ ├── navigation/ │ │ │ └── nav_graph.xml │ │ └── values/ │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test/ │ └── java/ │ └── net/ │ └── simplifiedcoding/ │ └── mvvmsampleapp/ │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.iml .gradle /local.properties /.idea/caches /.idea/libraries /.idea/modules.xml /.idea/workspace.xml /.idea/navEditor.xml /.idea/assetWizardSettings.xml .DS_Store /build /captures .externalNativeBuild ================================================ FILE: .idea/codeStyles/Project.xml ================================================
xmlns:android ^$
xmlns:.* ^$ BY_NAME
.*:id http://schemas.android.com/apk/res/android
.*:name http://schemas.android.com/apk/res/android
name ^$
style ^$
.* ^$ BY_NAME
.* http://schemas.android.com/apk/res/android ANDROID_ATTRIBUTE_ORDER
.* .* BY_NAME
================================================ FILE: .idea/codeStyles/codeStyleConfig.xml ================================================ ================================================ FILE: .idea/encodings.xml ================================================ ================================================ FILE: .idea/gradle.xml ================================================ ================================================ FILE: .idea/misc.xml ================================================ ================================================ FILE: .idea/runConfigurations.xml ================================================ ================================================ FILE: .idea/vcs.xml ================================================ ================================================ FILE: app/.gitignore ================================================ /build ================================================ FILE: app/build.gradle ================================================ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' //kotlin kapt and navigation safeargs plugin apply plugin: 'kotlin-kapt' apply plugin: "androidx.navigation.safeargs" android { compileSdkVersion 29 defaultConfig { applicationId "net.simplifiedcoding.mvvmsampleapp" minSdkVersion 26 targetSdkVersion 29 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } dataBinding { enabled = true } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.core:core-ktx:1.2.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' //Retrofit and GSON implementation 'com.squareup.retrofit2:retrofit:2.6.2' implementation 'com.squareup.retrofit2:converter-gson:2.6.2' //Kotlin Coroutines implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3" // ViewModel and LiveData implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" //New Material Design implementation 'com.google.android.material:material:1.2.0-alpha06' //Kodein Dependency Injection implementation "org.kodein.di:kodein-di-generic-jvm:6.2.1" implementation "org.kodein.di:kodein-di-framework-android-x:6.2.1" //Android Room implementation "androidx.room:room-runtime:2.2.5" implementation "androidx.room:room-ktx:2.2.5" kapt "androidx.room:room-compiler:2.2.5" //Android Navigation Architecture implementation "androidx.navigation:navigation-fragment-ktx:2.3.0-alpha06" implementation "androidx.navigation:navigation-ui-ktx:2.3.0-alpha06" implementation 'com.xwray:groupie:2.3.0' implementation 'com.xwray:groupie-kotlin-android-extensions:2.3.0' implementation 'com.xwray:groupie-databinding:2.3.0' implementation "androidx.preference:preference-ktx:1.1.1" } ================================================ FILE: app/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile ================================================ FILE: app/src/androidTest/java/net/simplifiedcoding/mvvmsampleapp/ExampleInstrumentedTest.kt ================================================ package net.simplifiedcoding.mvvmsampleapp import androidx.test.InstrumentationRegistry import androidx.test.runner.AndroidJUnit4 import org.junit.Test import org.junit.runner.RunWith import org.junit.Assert.* /** * Instrumented test, which will execute on an Android device. * * See [testing documentation](http://d.android.com/tools/testing). */ @RunWith(AndroidJUnit4::class) class ExampleInstrumentedTest { @Test fun useAppContext() { // Context of the app under test. val appContext = InstrumentationRegistry.getTargetContext() assertEquals("net.simplifiedcoding.mvvmsampleapp", appContext.packageName) } } ================================================ FILE: app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: app/src/main/java/net/simplifiedcoding/mvvmsampleapp/MVVMApplication.kt ================================================ package net.simplifiedcoding.mvvmsampleapp import android.app.Application import net.simplifiedcoding.mvvmsampleapp.data.db.AppDatabase import net.simplifiedcoding.mvvmsampleapp.data.network.MyApi import net.simplifiedcoding.mvvmsampleapp.data.network.NetworkConnectionInterceptor import net.simplifiedcoding.mvvmsampleapp.data.preferences.PreferenceProvider import net.simplifiedcoding.mvvmsampleapp.data.repositories.QuotesRepository import net.simplifiedcoding.mvvmsampleapp.data.repositories.UserRepository import net.simplifiedcoding.mvvmsampleapp.ui.auth.AuthViewModelFactory import net.simplifiedcoding.mvvmsampleapp.ui.home.profile.ProfileViewModelFactory import net.simplifiedcoding.mvvmsampleapp.ui.home.quotes.QuotesViewModelFactory import org.kodein.di.Kodein import org.kodein.di.KodeinAware import org.kodein.di.android.x.androidXModule import org.kodein.di.generic.bind import org.kodein.di.generic.instance import org.kodein.di.generic.provider import org.kodein.di.generic.singleton class MVVMApplication : Application(), KodeinAware { override val kodein = Kodein.lazy { import(androidXModule(this@MVVMApplication)) bind() from singleton { NetworkConnectionInterceptor(instance()) } bind() from singleton { MyApi(instance()) } bind() from singleton { AppDatabase(instance()) } bind() from singleton { PreferenceProvider(instance()) } bind() from singleton { UserRepository(instance(), instance()) } bind() from singleton { QuotesRepository(instance(), instance(), instance()) } bind() from provider { AuthViewModelFactory(instance()) } bind() from provider { ProfileViewModelFactory(instance()) } bind() from provider{ QuotesViewModelFactory(instance())} } } ================================================ FILE: app/src/main/java/net/simplifiedcoding/mvvmsampleapp/data/db/AppDatabase.kt ================================================ package net.simplifiedcoding.mvvmsampleapp.data.db import android.content.Context import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase import net.simplifiedcoding.mvvmsampleapp.data.db.entities.Quote import net.simplifiedcoding.mvvmsampleapp.data.db.entities.User @Database( entities = [User::class, Quote::class], version = 1 ) abstract class AppDatabase : RoomDatabase() { abstract fun getUserDao(): UserDao abstract fun getQuoteDao(): QuoteDao companion object { @Volatile private var instance: AppDatabase? = null private val LOCK = Any() operator fun invoke(context: Context) = instance ?: synchronized(LOCK) { instance ?: buildDatabase(context).also { instance = it } } private fun buildDatabase(context: Context) = Room.databaseBuilder( context.applicationContext, AppDatabase::class.java, "MyDatabase.db" ).build() } } ================================================ FILE: app/src/main/java/net/simplifiedcoding/mvvmsampleapp/data/db/QuoteDao.kt ================================================ package net.simplifiedcoding.mvvmsampleapp.data.db import androidx.lifecycle.LiveData import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query import net.simplifiedcoding.mvvmsampleapp.data.db.entities.Quote @Dao interface QuoteDao { @Insert(onConflict = OnConflictStrategy.REPLACE) fun saveAllQuotes(quotes : List) @Query("SELECT * FROM Quote") fun getQuotes() : LiveData> } ================================================ FILE: app/src/main/java/net/simplifiedcoding/mvvmsampleapp/data/db/UserDao.kt ================================================ package net.simplifiedcoding.mvvmsampleapp.data.db import androidx.lifecycle.LiveData import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query import net.simplifiedcoding.mvvmsampleapp.data.db.entities.CURRENT_USER_ID import net.simplifiedcoding.mvvmsampleapp.data.db.entities.User @Dao interface UserDao{ @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun upsert(user: User) : Long @Query("SELECT * FROM user WHERE uid = $CURRENT_USER_ID") fun getuser() : LiveData } ================================================ FILE: app/src/main/java/net/simplifiedcoding/mvvmsampleapp/data/db/entities/Quote.kt ================================================ package net.simplifiedcoding.mvvmsampleapp.data.db.entities import androidx.room.Entity import androidx.room.PrimaryKey @Entity data class Quote( @PrimaryKey(autoGenerate = false) val id: Int, val quote: String, val author: String, val thumbnail: String, val created_at: String?, val updated_at: String? ) ================================================ FILE: app/src/main/java/net/simplifiedcoding/mvvmsampleapp/data/db/entities/User.kt ================================================ package net.simplifiedcoding.mvvmsampleapp.data.db.entities import androidx.room.Entity import androidx.room.PrimaryKey const val CURRENT_USER_ID = 0 @Entity data class User( var id: Int? = null, var name: String? = null, var email: String? = null, var password: String? = null, var email_verified_at: String? = null, var created_at: String? = null, var updated_at: String? = null ){ @PrimaryKey(autoGenerate = false) var uid: Int = CURRENT_USER_ID } ================================================ FILE: app/src/main/java/net/simplifiedcoding/mvvmsampleapp/data/network/MyApi.kt ================================================ package net.simplifiedcoding.mvvmsampleapp.data.network import net.simplifiedcoding.mvvmsampleapp.data.network.responses.AuthResponse import net.simplifiedcoding.mvvmsampleapp.data.network.responses.QuotesResponse import okhttp3.OkHttpClient import okhttp3.ResponseBody import retrofit2.Call import retrofit2.Response import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import retrofit2.http.Field import retrofit2.http.FormUrlEncoded import retrofit2.http.GET import retrofit2.http.POST interface MyApi { @FormUrlEncoded @POST("login") suspend fun userLogin( @Field("email") email: String, @Field("password") password: String ) : Response @FormUrlEncoded @POST("signup") suspend fun userSignup( @Field("name") name: String, @Field("email") email: String, @Field("password") password: String ) : Response @GET("quotes") suspend fun getQuotes() : Response companion object{ operator fun invoke( networkConnectionInterceptor: NetworkConnectionInterceptor ) : MyApi{ val okkHttpclient = OkHttpClient.Builder() .addInterceptor(networkConnectionInterceptor) .build() return Retrofit.Builder() .client(okkHttpclient) .baseUrl("https://api.simplifiedcoding.in/course-apis/mvvm/") .addConverterFactory(GsonConverterFactory.create()) .build() .create(MyApi::class.java) } } } ================================================ FILE: app/src/main/java/net/simplifiedcoding/mvvmsampleapp/data/network/NetworkConnectionInterceptor.kt ================================================ package net.simplifiedcoding.mvvmsampleapp.data.network import android.content.Context import android.net.ConnectivityManager import android.net.NetworkCapabilities import net.simplifiedcoding.mvvmsampleapp.util.NoInternetException import okhttp3.Interceptor import okhttp3.Response class NetworkConnectionInterceptor( context: Context ) : Interceptor { private val applicationContext = context.applicationContext override fun intercept(chain: Interceptor.Chain): Response { if (!isInternetAvailable()) throw NoInternetException("Make sure you have an active data connection") return chain.proceed(chain.request()) } private fun isInternetAvailable(): Boolean { var result = false val connectivityManager = applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? connectivityManager?.let { it.getNetworkCapabilities(connectivityManager.activeNetwork)?.apply { result = when { hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true else -> false } } } return result } } ================================================ FILE: app/src/main/java/net/simplifiedcoding/mvvmsampleapp/data/network/SafeApiRequest.kt ================================================ package net.simplifiedcoding.mvvmsampleapp.data.network import net.simplifiedcoding.mvvmsampleapp.util.ApiException import org.json.JSONException import org.json.JSONObject import retrofit2.Response abstract class SafeApiRequest { suspend fun apiRequest(call: suspend () -> Response) : T{ val response = call.invoke() if(response.isSuccessful){ return response.body()!! }else{ val error = response.errorBody()?.string() val message = StringBuilder() error?.let{ try{ message.append(JSONObject(it).getString("message")) }catch(e: JSONException){ } message.append("\n") } message.append("Error Code: ${response.code()}") throw ApiException(message.toString()) } } } ================================================ FILE: app/src/main/java/net/simplifiedcoding/mvvmsampleapp/data/network/responses/AuthResponse.kt ================================================ package net.simplifiedcoding.mvvmsampleapp.data.network.responses import net.simplifiedcoding.mvvmsampleapp.data.db.entities.User data class AuthResponse( val isSuccessful : Boolean?, val message: String?, val user: User? ) ================================================ FILE: app/src/main/java/net/simplifiedcoding/mvvmsampleapp/data/network/responses/QuotesResponse.kt ================================================ package net.simplifiedcoding.mvvmsampleapp.data.network.responses import net.simplifiedcoding.mvvmsampleapp.data.db.entities.Quote data class QuotesResponse ( val isSuccessful: Boolean, val quotes: List ) ================================================ FILE: app/src/main/java/net/simplifiedcoding/mvvmsampleapp/data/preferences/PreferenceProvider.kt ================================================ package net.simplifiedcoding.mvvmsampleapp.data.preferences import android.content.Context import android.content.SharedPreferences import androidx.preference.PreferenceManager private const val KEY_SAVED_AT = "key_saved_at" class PreferenceProvider( context: Context ) { private val appContext = context.applicationContext private val preference: SharedPreferences get() = PreferenceManager.getDefaultSharedPreferences(appContext) fun savelastSavedAt(savedAt: String) { preference.edit().putString( KEY_SAVED_AT, savedAt ).apply() } fun getLastSavedAt(): String? { return preference.getString(KEY_SAVED_AT, null) } } ================================================ FILE: app/src/main/java/net/simplifiedcoding/mvvmsampleapp/data/repositories/QuotesRepository.kt ================================================ package net.simplifiedcoding.mvvmsampleapp.data.repositories import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import net.simplifiedcoding.mvvmsampleapp.data.db.AppDatabase import net.simplifiedcoding.mvvmsampleapp.data.db.entities.Quote import net.simplifiedcoding.mvvmsampleapp.data.network.MyApi import net.simplifiedcoding.mvvmsampleapp.data.network.SafeApiRequest import net.simplifiedcoding.mvvmsampleapp.data.preferences.PreferenceProvider import net.simplifiedcoding.mvvmsampleapp.util.Coroutines import java.lang.Exception import java.time.LocalDateTime import java.time.temporal.ChronoUnit private val MINIMUM_INTERVAL = 6 class QuotesRepository( private val api: MyApi, private val db: AppDatabase, private val prefs: PreferenceProvider ) : SafeApiRequest() { private val quotes = MutableLiveData>() init { quotes.observeForever { saveQuotes(it) } } suspend fun getQuotes(): LiveData> { return withContext(Dispatchers.IO) { fetchQuotes() db.getQuoteDao().getQuotes() } } private suspend fun fetchQuotes() { val lastSavedAt = prefs.getLastSavedAt() if (lastSavedAt == null || isFetchNeeded(LocalDateTime.parse(lastSavedAt))) { try { val response = apiRequest { api.getQuotes() } quotes.postValue(response.quotes) } catch (e: Exception) { e.printStackTrace() } } } private fun isFetchNeeded(savedAt: LocalDateTime): Boolean { return ChronoUnit.HOURS.between(savedAt, LocalDateTime.now()) > MINIMUM_INTERVAL } private fun saveQuotes(quotes: List) { Coroutines.io { prefs.savelastSavedAt(LocalDateTime.now().toString()) db.getQuoteDao().saveAllQuotes(quotes) } } } ================================================ FILE: app/src/main/java/net/simplifiedcoding/mvvmsampleapp/data/repositories/UserRepository.kt ================================================ package net.simplifiedcoding.mvvmsampleapp.data.repositories import net.simplifiedcoding.mvvmsampleapp.data.db.AppDatabase import net.simplifiedcoding.mvvmsampleapp.data.db.entities.User import net.simplifiedcoding.mvvmsampleapp.data.network.MyApi import net.simplifiedcoding.mvvmsampleapp.data.network.SafeApiRequest import net.simplifiedcoding.mvvmsampleapp.data.network.responses.AuthResponse import retrofit2.Response class UserRepository( private val api: MyApi, private val db: AppDatabase ) : SafeApiRequest() { suspend fun userLogin(email: String, password: String): AuthResponse { return apiRequest { api.userLogin(email, password) } } suspend fun userSignup( name: String, email: String, password: String ) : AuthResponse { return apiRequest{ api.userSignup(name, email, password)} } suspend fun saveUser(user: User) = db.getUserDao().upsert(user) fun getUser() = db.getUserDao().getuser() } ================================================ FILE: app/src/main/java/net/simplifiedcoding/mvvmsampleapp/ui/auth/AuthViewModel.kt ================================================ package net.simplifiedcoding.mvvmsampleapp.ui.auth import androidx.lifecycle.ViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import net.simplifiedcoding.mvvmsampleapp.data.db.entities.User import net.simplifiedcoding.mvvmsampleapp.data.repositories.UserRepository class AuthViewModel( private val repository: UserRepository ) : ViewModel() { fun getLoggedInUser() = repository.getUser() suspend fun userLogin( email: String, password: String ) = withContext(Dispatchers.IO) { repository.userLogin(email, password) } suspend fun userSignup( name: String, email: String, password: String ) = withContext(Dispatchers.IO) { repository.userSignup(name, email, password) } suspend fun saveLoggedInUser(user: User) = repository.saveUser(user) } ================================================ FILE: app/src/main/java/net/simplifiedcoding/mvvmsampleapp/ui/auth/AuthViewModelFactory.kt ================================================ package net.simplifiedcoding.mvvmsampleapp.ui.auth import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import net.simplifiedcoding.mvvmsampleapp.data.repositories.UserRepository @Suppress("UNCHECKED_CAST") class AuthViewModelFactory( private val repository: UserRepository ) : ViewModelProvider.NewInstanceFactory() { override fun create(modelClass: Class): T { return AuthViewModel(repository) as T } } ================================================ FILE: app/src/main/java/net/simplifiedcoding/mvvmsampleapp/ui/auth/LoginActivity.kt ================================================ package net.simplifiedcoding.mvvmsampleapp.ui.auth import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.databinding.DataBindingUtil import androidx.lifecycle.* import kotlinx.android.synthetic.main.activity_login.* import kotlinx.coroutines.launch import net.simplifiedcoding.mvvmsampleapp.R import net.simplifiedcoding.mvvmsampleapp.data.db.entities.User import net.simplifiedcoding.mvvmsampleapp.databinding.ActivityLoginBinding import net.simplifiedcoding.mvvmsampleapp.ui.home.HomeActivity import net.simplifiedcoding.mvvmsampleapp.util.* import org.kodein.di.KodeinAware import org.kodein.di.android.kodein import org.kodein.di.generic.instance class LoginActivity : AppCompatActivity(), KodeinAware { override val kodein by kodein() private val factory: AuthViewModelFactory by instance() private lateinit var binding: ActivityLoginBinding private lateinit var viewModel: AuthViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = DataBindingUtil.setContentView(this, R.layout.activity_login) viewModel = ViewModelProvider(this, factory).get(AuthViewModel::class.java) viewModel.getLoggedInUser().observe(this, Observer { user -> if (user != null) { Intent(this, HomeActivity::class.java).also { it.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK startActivity(it) } } }) binding.buttonSignIn.setOnClickListener { loginUser() } binding.textViewSignUp.setOnClickListener { startActivity(Intent(this, SignupActivity::class.java)) } } private fun loginUser() { val email = binding.editTextEmail.text.toString().trim() val password = binding.editTextPassword.text.toString().trim() lifecycleScope.launch { try { val authResponse = viewModel.userLogin(email, password) if (authResponse.user != null) { viewModel.saveLoggedInUser(authResponse.user) } else { binding.rootLayout.snackbar(authResponse.message!!) } } catch (e: ApiException) { e.printStackTrace() } catch (e: NoInternetException) { e.printStackTrace() } } } } ================================================ FILE: app/src/main/java/net/simplifiedcoding/mvvmsampleapp/ui/auth/SignupActivity.kt ================================================ package net.simplifiedcoding.mvvmsampleapp.ui.auth import android.content.Intent import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import androidx.databinding.DataBindingUtil import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.lifecycleScope import kotlinx.android.synthetic.main.activity_login.* import kotlinx.coroutines.launch import net.simplifiedcoding.mvvmsampleapp.R import net.simplifiedcoding.mvvmsampleapp.data.db.entities.User import net.simplifiedcoding.mvvmsampleapp.databinding.ActivitySignupBinding import net.simplifiedcoding.mvvmsampleapp.ui.home.HomeActivity import net.simplifiedcoding.mvvmsampleapp.util.* import org.kodein.di.KodeinAware import org.kodein.di.android.kodein import org.kodein.di.generic.instance class SignupActivity : AppCompatActivity(), KodeinAware { override val kodein by kodein() private val factory: AuthViewModelFactory by instance() private lateinit var binding: ActivitySignupBinding private lateinit var viewModel: AuthViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = DataBindingUtil.setContentView(this, R.layout.activity_signup) viewModel = ViewModelProvider(this, factory).get(AuthViewModel::class.java) viewModel.getLoggedInUser().observe(this, Observer { user -> if (user != null) { Intent(this, HomeActivity::class.java).also { it.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK startActivity(it) } } }) binding.buttonSignUp.setOnClickListener { userSignup() } } private fun userSignup() { val name = binding.editTextName.text.toString().trim() val email = binding.editTextEmail.text.toString().trim() val password = binding.editTextPassword.text.toString().trim() val password1 = binding.editTextPassword.text.toString().trim() //@todo add input validations lifecycleScope.launch { try { val authResponse = viewModel.userSignup(name, email, password) if (authResponse.user != null) { viewModel.saveLoggedInUser(authResponse.user) } else { binding.root.snackbar(authResponse.message!!) } } catch (e: ApiException) { e.printStackTrace() } catch (e: NoInternetException) { e.printStackTrace() } } } } ================================================ FILE: app/src/main/java/net/simplifiedcoding/mvvmsampleapp/ui/home/HomeActivity.kt ================================================ package net.simplifiedcoding.mvvmsampleapp.ui.home import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import androidx.navigation.Navigation import androidx.navigation.ui.NavigationUI import kotlinx.android.synthetic.main.activity_home.* import net.simplifiedcoding.mvvmsampleapp.R class HomeActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_home) setSupportActionBar(toolbar) val navController = Navigation.findNavController(this, R.id.fragment) NavigationUI.setupWithNavController(nav_view, navController) NavigationUI.setupActionBarWithNavController(this,navController, drawer_layout) } override fun onSupportNavigateUp(): Boolean { return NavigationUI.navigateUp( Navigation.findNavController(this, R.id.fragment), drawer_layout ) } } ================================================ FILE: app/src/main/java/net/simplifiedcoding/mvvmsampleapp/ui/home/profile/ProfileFragment.kt ================================================ package net.simplifiedcoding.mvvmsampleapp.ui.home.profile import androidx.lifecycle.ViewModelProviders import android.os.Bundle import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.databinding.DataBindingUtil import net.simplifiedcoding.mvvmsampleapp.R import net.simplifiedcoding.mvvmsampleapp.databinding.ProfileFragmentBinding import org.kodein.di.android.x.kodein import org.kodein.di.KodeinAware import org.kodein.di.generic.instance class ProfileFragment : Fragment(), KodeinAware { override val kodein by kodein() private lateinit var viewModel: ProfileViewModel private val factory: ProfileViewModelFactory by instance() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { val binding: ProfileFragmentBinding = DataBindingUtil.inflate(inflater, R.layout.profile_fragment, container, false) viewModel = ViewModelProviders.of(this, factory).get(ProfileViewModel::class.java) binding.viewmodel = viewModel binding.lifecycleOwner = this return binding.root } } ================================================ FILE: app/src/main/java/net/simplifiedcoding/mvvmsampleapp/ui/home/profile/ProfileViewModel.kt ================================================ package net.simplifiedcoding.mvvmsampleapp.ui.home.profile import androidx.lifecycle.ViewModel; import net.simplifiedcoding.mvvmsampleapp.data.repositories.UserRepository class ProfileViewModel( repository: UserRepository ) : ViewModel() { val user = repository.getUser() } ================================================ FILE: app/src/main/java/net/simplifiedcoding/mvvmsampleapp/ui/home/profile/ProfileViewModelFactory.kt ================================================ package net.simplifiedcoding.mvvmsampleapp.ui.home.profile import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import net.simplifiedcoding.mvvmsampleapp.data.repositories.UserRepository @Suppress("UNCHECKED_CAST") class ProfileViewModelFactory( private val repository: UserRepository ) : ViewModelProvider.NewInstanceFactory() { override fun create(modelClass: Class): T { return ProfileViewModel(repository) as T } } ================================================ FILE: app/src/main/java/net/simplifiedcoding/mvvmsampleapp/ui/home/quotes/QuoteItem.kt ================================================ package net.simplifiedcoding.mvvmsampleapp.ui.home.quotes import com.xwray.groupie.databinding.BindableItem import net.simplifiedcoding.mvvmsampleapp.R import net.simplifiedcoding.mvvmsampleapp.data.db.entities.Quote import net.simplifiedcoding.mvvmsampleapp.databinding.ItemQuoteBinding class QuoteItem( private val quote: Quote ) : BindableItem(){ override fun getLayout() = R.layout.item_quote override fun bind(viewBinding: ItemQuoteBinding, position: Int) { viewBinding.setQuote(quote) } } ================================================ FILE: app/src/main/java/net/simplifiedcoding/mvvmsampleapp/ui/home/quotes/QuotesFragment.kt ================================================ package net.simplifiedcoding.mvvmsampleapp.ui.home.quotes import androidx.lifecycle.ViewModelProviders import android.os.Bundle import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.lifecycle.Observer import androidx.recyclerview.widget.LinearLayoutManager import com.xwray.groupie.GroupAdapter import com.xwray.groupie.ViewHolder import kotlinx.android.synthetic.main.quotes_fragment.* import net.simplifiedcoding.mvvmsampleapp.R import net.simplifiedcoding.mvvmsampleapp.data.db.entities.Quote import net.simplifiedcoding.mvvmsampleapp.util.Coroutines import net.simplifiedcoding.mvvmsampleapp.util.hide import net.simplifiedcoding.mvvmsampleapp.util.show import net.simplifiedcoding.mvvmsampleapp.util.toast import org.kodein.di.KodeinAware import org.kodein.di.android.x.kodein import org.kodein.di.generic.instance class QuotesFragment : Fragment(), KodeinAware { override val kodein by kodein() private val factory: QuotesViewModelFactory by instance() private lateinit var viewModel: QuotesViewModel override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return inflater.inflate(R.layout.quotes_fragment, container, false) } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) viewModel = ViewModelProviders.of(this, factory).get(QuotesViewModel::class.java) bindUI() } private fun bindUI() = Coroutines.main { progress_bar.show() viewModel.quotes.await().observe(this, Observer { progress_bar.hide() initRecyclerView(it.toQuoteItem()) }) } private fun initRecyclerView(quoteItem: List) { val mAdapter = GroupAdapter().apply { addAll(quoteItem) } recyclerview.apply { layoutManager = LinearLayoutManager(context) setHasFixedSize(true) adapter = mAdapter } } private fun List.toQuoteItem() : List{ return this.map { QuoteItem(it) } } } ================================================ FILE: app/src/main/java/net/simplifiedcoding/mvvmsampleapp/ui/home/quotes/QuotesViewModel.kt ================================================ package net.simplifiedcoding.mvvmsampleapp.ui.home.quotes import androidx.lifecycle.ViewModel; import net.simplifiedcoding.mvvmsampleapp.data.repositories.QuotesRepository import net.simplifiedcoding.mvvmsampleapp.util.lazyDeferred class QuotesViewModel( repository: QuotesRepository ) : ViewModel() { val quotes by lazyDeferred { repository.getQuotes() } } ================================================ FILE: app/src/main/java/net/simplifiedcoding/mvvmsampleapp/ui/home/quotes/QuotesViewModelFactory.kt ================================================ package net.simplifiedcoding.mvvmsampleapp.ui.home.quotes import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import net.simplifiedcoding.mvvmsampleapp.data.repositories.QuotesRepository import net.simplifiedcoding.mvvmsampleapp.data.repositories.UserRepository @Suppress("UNCHECKED_CAST") class QuotesViewModelFactory( private val repository: QuotesRepository ) : ViewModelProvider.NewInstanceFactory() { override fun create(modelClass: Class): T { return QuotesViewModel(repository) as T } } ================================================ FILE: app/src/main/java/net/simplifiedcoding/mvvmsampleapp/util/Coroutines.kt ================================================ package net.simplifiedcoding.mvvmsampleapp.util import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch object Coroutines { fun main(work: suspend (() -> Unit)) = CoroutineScope(Dispatchers.Main).launch { work() } fun io(work: suspend (() -> Unit)) = CoroutineScope(Dispatchers.IO).launch { work() } } ================================================ FILE: app/src/main/java/net/simplifiedcoding/mvvmsampleapp/util/Delegates.kt ================================================ package net.simplifiedcoding.mvvmsampleapp.util import kotlinx.coroutines.* fun lazyDeferred(block: suspend CoroutineScope.() -> T): Lazy>{ return lazy { GlobalScope.async(start = CoroutineStart.LAZY) { block.invoke(this) } } } ================================================ FILE: app/src/main/java/net/simplifiedcoding/mvvmsampleapp/util/Exceptions.kt ================================================ package net.simplifiedcoding.mvvmsampleapp.util import java.io.IOException class ApiException(message: String) : IOException(message) class NoInternetException(message: String) : IOException(message) ================================================ FILE: app/src/main/java/net/simplifiedcoding/mvvmsampleapp/util/ViewUtils.kt ================================================ package net.simplifiedcoding.mvvmsampleapp.util import android.content.Context import android.view.View import android.widget.ProgressBar import android.widget.Toast import com.google.android.material.snackbar.Snackbar fun Context.toast(message: String){ Toast.makeText(this, message, Toast.LENGTH_LONG ).show() } fun ProgressBar.show(){ visibility = View.VISIBLE } fun ProgressBar.hide(){ visibility = View.GONE } fun View.snackbar(message: String){ Snackbar.make(this, message, Snackbar.LENGTH_LONG).also { snackbar -> snackbar.setAction("Ok") { snackbar.dismiss() } }.show() } ================================================ FILE: app/src/main/res/drawable/edit_text_round_gray_background.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_app_logo.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_email.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_launcher_background.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_lock.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_name.xml ================================================ ================================================ FILE: app/src/main/res/drawable-v24/ic_launcher_foreground.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_home.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_login.xml ================================================