Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
b608d04
Added Room library and extracted the libraries version inside build.g…
LoredanaZdranc Feb 24, 2021
9d38387
Implemented a bottom navigation bar with two items (users & favorites…
LoredanaZdranc Feb 24, 2021
9058925
Added favorite icon for each Github User item
LoredanaZdranc Feb 24, 2021
aaff585
Created the Entity class for Room database -> Github Users
LoredanaZdranc Feb 24, 2021
55fb6ea
Merge branch 'develop' into feature/local_persistance
LoredanaZdranc Feb 24, 2021
e5c653a
Implemented GithubUsersDatabase and its corresponding DAO. Fixed the …
LoredanaZdranc Feb 26, 2021
5818ff3
Added entities to GihubUsersDatabase
LoredanaZdranc Feb 26, 2021
8a04350
Implemented new entity for saving the Github users list into local da…
LoredanaZdranc Mar 4, 2021
0c22789
Removed unused ApiResponse, Error, NetworkResponseAdapter, NetworkRes…
LoredanaZdranc Mar 4, 2021
6a5ba95
Renamed favorite related classes to Settings. Implemented the UI for …
LoredanaZdranc Mar 11, 2021
a5437d6
Implemented single choice selection for language settings. Persist la…
LoredanaZdranc Mar 11, 2021
1e04499
Set full screen app mode
LoredanaZdranc Mar 11, 2021
d361d84
Changed the UI of Github users list screen by adding a title above th…
LoredanaZdranc Mar 12, 2021
65625b6
Implemented a general way to display a loading progress bar directly …
LoredanaZdranc Mar 15, 2021
4584c30
Implemented a general way to display messages in the app. Handled the…
LoredanaZdranc Mar 15, 2021
7d5ee9c
Increased the version code of some libraries
LoredanaZdranc Mar 17, 2021
a76fa2a
Removed unused BaseActivity class
LoredanaZdranc Mar 17, 2021
d54647f
Commented the BaseFragment class
LoredanaZdranc Mar 17, 2021
99f6bc6
Commented BaseViewModel class and methods
LoredanaZdranc Mar 17, 2021
74beeb6
Cleaned the code
LoredanaZdranc Mar 17, 2021
d3c0e2d
Removed unused ressources
LoredanaZdranc Mar 23, 2021
42825a5
Removed allowBackup property in AndroidManifest
LoredanaZdranc Mar 26, 2021
bfd92a6
Implemented ExceptionHandler as interface and created its implementation
LoredanaZdranc Mar 26, 2021
7791128
Removed NetworkHandler class because it doesn't make sense to check i…
LoredanaZdranc Mar 26, 2021
b3c9610
Renamed EmptyLocalGithubUsersLisException to EmptyLocalGithubUsersLis…
LoredanaZdranc Mar 29, 2021
8a037fc
Removed Exception class and implemented a sealed class to handle only…
LoredanaZdranc Mar 29, 2021
0f21204
Catch only java.io.IOException to avoid a too general catch.
LoredanaZdranc Mar 29, 2021
02bcc9e
Implemented a general way to display the messages directly from ViewM…
LoredanaZdranc Mar 29, 2021
9266a8b
Implemented an extension function for GithubUserResponse to map it to…
LoredanaZdranc Mar 29, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 23 additions & 20 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -40,41 +40,44 @@ android {
dependencies {

implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation "androidx.core:core-ktx:$core_ktx"
implementation "androidx.appcompat:appcompat:$appcompat"
implementation "com.google.android.material:material:$material"
implementation "androidx.constraintlayout:constraintlayout:$constraint_layout"
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
androidTestImplementation "androidx.test.ext:junit:$junit"
androidTestImplementation "androidx.test.espresso:espresso-core:$espresso"

// Navigation
implementation "androidx.navigation:navigation-fragment-ktx:$navigationVersion"
implementation "androidx.navigation:navigation-ui-ktx:$navigationVersion"

// Koin
implementation "org.koin:koin-android:2.0.1"
implementation 'org.koin:koin-androidx-viewmodel:2.0.1'
implementation 'org.koin:koin-androidx-scope:2.0.1'
implementation "org.koin:koin-android:$koin"
implementation "org.koin:koin-androidx-viewmodel:$koin"
implementation "org.koin:koin-androidx-scope:$koin"

//Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.6.1'
implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0'

// Gson
implementation 'com.squareup.retrofit2:converter-gson:2.6.1'
implementation "com.squareup.retrofit2:retrofit:$retrofit"
implementation "com.squareup.retrofit2:converter-gson:$retrofit"
implementation "com.squareup.okhttp3:logging-interceptor:$okhttp"

//Glide
implementation 'com.github.bumptech.glide:glide:4.11.0'
kapt 'com.github.bumptech.glide:compiler:4.11.0'
implementation "com.github.bumptech.glide:glide:$glide"
kapt "com.github.bumptech.glide:compiler:$glide"

//Hawk
implementation 'com.orhanobut:hawk:2.0.1'
implementation "com.orhanobut:hawk:$hawk"

//Timber
implementation 'com.jakewharton.timber:timber:4.7.1'
implementation "com.jakewharton.timber:timber:$timber"

//Calligraphy
implementation 'io.github.inflationx:calligraphy3:3.1.1'
implementation 'io.github.inflationx:viewpump:2.0.3'
implementation "io.github.inflationx:calligraphy3:$calligraphy"
implementation "io.github.inflationx:viewpump:$viewPump"

implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version"

}
3 changes: 1 addition & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@
<uses-permission android:name="android.permission.INTERNET" />

<application
android:allowBackup="true"
android:name=".App"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.BaseAndroidTemplate">
android:theme="@style/AppTheme.FullScreen">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Expand Down
7 changes: 4 additions & 3 deletions app/src/main/java/com/base_android_template/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.base_android_template
import android.app.Application
import android.content.res.Configuration
import com.base_android_template.di.getAppModules
import com.base_android_template.shared.HawkKeys
import com.base_android_template.shared.Locales
import com.base_android_template.utils.language.LocaleUtils
import com.orhanobut.hawk.Hawk
Expand All @@ -22,10 +23,10 @@ class App : Application() {
modules(getAppModules())
}

initLocale()

Hawk.init(this).build()

initLocale()

initCalligraphy()
}

Expand All @@ -35,7 +36,7 @@ class App : Application() {
}

private fun initLocale() {
val locale = Locale(Locales.ENGLISH)
val locale = Locale(Hawk.get(HawkKeys.HAWK_PREF_LOCALE, Locales.ENGLISH))
LocaleUtils.setLocale(locale)
LocaleUtils.updateConfig(this, baseContext.resources.configuration)
}
Expand Down
35 changes: 14 additions & 21 deletions app/src/main/java/com/base_android_template/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package com.base_android_template

import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import com.base_android_template.shared.Locales
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.NavigationUI
import com.base_android_template.utils.language.LocaleUtils
import java.util.Locale
import com.google.android.material.bottomnavigation.BottomNavigationView


class MainActivity : AppCompatActivity() {
Expand All @@ -18,26 +17,20 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}

override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.language_menu, menu)
return true
setUpNavigation()
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.english -> {
LocaleUtils.setLocale(Locale(Locales.ENGLISH))
recreate()
true
}
R.id.romanian -> {
LocaleUtils.setLocale(Locale(Locales.ROMANIAN))
recreate()
true
}
else -> super.onOptionsItemSelected(item)
private fun setUpNavigation() {
val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottom_navigation)
val navHostFragment = supportFragmentManager
.findFragmentById(R.id.main_nav_host_fragment) as NavHostFragment?
navHostFragment?.navController?.let {
NavigationUI.setupWithNavController(
bottomNavigationView,
it
)
}
}

}
11 changes: 0 additions & 11 deletions app/src/main/java/com/base_android_template/api/GithubUsersApi.kt

This file was deleted.

This file was deleted.

74 changes: 73 additions & 1 deletion app/src/main/java/com/base_android_template/base/BaseFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,51 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.annotation.LayoutRes
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.navigation.fragment.findNavController
import com.base_android_template.BR
import com.base_android_template.shared.loading.UILoading
import com.base_android_template.shared.model.NavigationCommand
import org.koin.android.ext.android.inject

/**
* Represents the base class that will be extended by any Fragment in the app.
* It receives the corresponding ViewDataBinding and ViewModel generic types
* and the id of the layout to be inflated
*
* @param layoutResId Int. The id of the layout that defines the structure
* for the user interface of the fragment
*/
abstract class BaseFragment<VB : ViewDataBinding, VM : BaseViewModel>(@LayoutRes private val layoutResId: Int) :
Fragment() {

private var binding: VB? = null
protected abstract val viewModel: VM
private val loadingDialog: UILoading by inject()

/**
* Receives the value from loading LiveData variable inside BaseViewModel
* and display the loading progress bar if value is true, and hide it otherwise
*/
private val loadingObserver: Observer<Boolean> = Observer { showLoading ->
if (showLoading) {
loadingDialog.show()
return@Observer
}

loadingDialog.hide()
}

/**
* Receives the value from navigationCommand LiveData variable inside BaseViewModel
* and, depending on NavigationCommand, proceed the navigation
*/
private val commandObserver = Observer<NavigationCommand?> { command ->
if (command != null) {
when (command) {
Expand All @@ -29,10 +59,45 @@ abstract class BaseFragment<VB : ViewDataBinding, VM : BaseViewModel>(@LayoutRes
findNavController().navigate(command.direction)
}
}
viewModel.clearLastNavigationCommand()
}
}

/**
* Receives the value from message LiveData variable inside BaseViewModel
* and displays the messages in a Toast
*/
private val messageObserver = Observer<String> {
(activity as? AppCompatActivity)?.apply {
if (it.isNotEmpty()) {
Toast.makeText(this, it, Toast.LENGTH_LONG).show()
}
}
}

/**
* Receives the value from messageResId LiveData variable inside BaseViewModel
* and get the corresponding string to the id and displays the messages in a Toast
*/
private val messageResIdObserver = Observer<Int> {
(activity as? AppCompatActivity)?.apply {
if (it != -1) {
if (getString(it) != "") {
Toast.makeText(this, it, Toast.LENGTH_LONG).show()
}
}
}
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

context?.let { loadingDialog.init(it) }
}

/**
* Using DataBindingUtil, inflate the layout by creating the corresponding ViewDataBinding
* Set always used "viewModel" DataBinding variable
*/
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
Expand All @@ -49,14 +114,21 @@ abstract class BaseFragment<VB : ViewDataBinding, VM : BaseViewModel>(@LayoutRes

viewModel.apply {
navigationCommand.observe(viewLifecycleOwner, commandObserver)
loading.observe(viewLifecycleOwner, loadingObserver)
message.observe(viewLifecycleOwner, messageObserver)
messageResId.observe(viewLifecycleOwner, messageResIdObserver)
}
}

/**
* Access the binding object from all subclasses
*/
protected fun requireBinding(): VB =
binding ?: throw NullPointerException("View is in destroyed state and the Binding is null")

override fun onDestroyView() {
super.onDestroyView()
loadingDialog.cancel()
binding = null
}

Expand Down
60 changes: 56 additions & 4 deletions app/src/main/java/com/base_android_template/base/BaseViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,75 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.navigation.NavDirections
import com.base_android_template.shared.model.NavigationCommand
import com.base_android_template.utils.SingleLiveEvent

/**
* Represents the base class that will be extended by any ViewModel in the app.
* It contains public methods that can be called from all ViewModels in the hierarchy
* to send commands to the BaseFragment
*
*/
open class BaseViewModel : ViewModel() {

val navigationCommand: LiveData<NavigationCommand?>
val navigationCommand: SingleLiveEvent<NavigationCommand?>
get() = _navigationCommand
val loading: SingleLiveEvent<Boolean>
get() = _loading
val message: SingleLiveEvent<String>
get() = _message
val messageResId: SingleLiveEvent<Int>
get() = _messageResId

private val _navigationCommand = MutableLiveData<NavigationCommand?>()
private val _navigationCommand = SingleLiveEvent<NavigationCommand?>()
private val _loading = SingleLiveEvent<Boolean>()
private val _message = SingleLiveEvent<String>()
private val _messageResId = SingleLiveEvent<Int>()

/**
* Call this method when want to navigate between fragments via NavDirections
*
* @param direction NavDirections. The generated method for navigation action
* that you've defined in the nav graph
*/
fun postNavigationCommand(direction: NavDirections) {
_navigationCommand.postValue(NavigationCommand.PerformNavAction(direction))
}

/**
* Call this method when want to navigate up in fragments
*
*/
fun postNavigateUpCommand() {
_navigationCommand.postValue(NavigationCommand.PerformNavUp)
}

fun clearLastNavigationCommand() {
_navigationCommand.value = null
/**
* Call this method when want to display a message in a Toast
*
* @param toDisplay String. The message to be displayed
*/
fun postMessage(toDisplay: String) {
_message.postValue(toDisplay)
}

/**
* Call this method when want to display a message in a Toast
*
* @param id Int. The id of the string to be displayed
*/
fun postMessageResId(id: Int) {
_messageResId.postValue(id)
}

/**
* Call this method when want to show/hide the loading progress bar
*
* @param loading Boolean. If:
* - true: show the loading progress bar
* - false: hide th loading progress bar
*/
fun postLoading(loading: Boolean) {
_loading.postValue(loading)
}

}
Loading