Gradle Dependency Management
1. Create a root level directory called buildSrc
2. Inside buildSrc create the file build.gradle.kts and enable the kotlin-dsl plugin with the snippet in build.gradle.kts.
3. Create file Dependencies.kt for define libraries
4. Can code build gradle by kotlin language (not implement)
Include all entities in project
Implement Room to management database.
Implement Retrofit to get data from api
1. Handle get data from api and save in database.
2. Expose data result
1. Wrapper get data from local database and get data from api.
2. Data result always in object Resource
3. We can force always load from network or local.
example in UserGitRepositoryImpl
1. Util class.
2. Base class.
3. views need to re-use (dialog..)
4. Extension
Main module.
1. AndroidManifes.xml
2. Activities, Fragment, Viewmodel.
3. layout
4. Dependency injection.
5. Use case class.
Dependency injection: Dagger
AppComponent: with 2 modules: AppModule::class, ActivityProvider::class
1. AppModule: with modules network, database, repository, viewModel
2. ActivityProvider: provide activities, FragmentProvider.
1. Binding data from viewmodel to layout with two ways.
2. handle action to view (click, input text)
add this code into root of view fragment.
<data>
<variable
name="viewModel"
type="com.example.gear_kotlin.viewmodel.ThirdFragmentViewModel" />
</data>
- One class _fragmentName_Binding will automatic create.
Example is: ThirdFragmentBinding
And in Fragment: thirdFragmentBinding.viewModel = viewModel
Example: ThirdFragment and layout: fragment_third
File location: res/navigation
1 activity has 1 nav_graph.
Actions: define action to replace fragment.
Arguments: this is safe-args, it's send data between fragments.
1. After build, it will auto generate file Directions with name: fragmentName + Directions,
example: AppFirstFragmentDirections
2. Call action with parameter like this:
val action = AppFirstFragmentDirections.setParamInt(param1).setParamStr(param2)
view.findViewById<Button>(R.id.button_first).setOnClickListener {findNavController().navigate(action)}
3. Get data from args like this:
val safeArgs: AppSecondFragmentArgs by navArgs()
val paramInt = safeArgs.paramInt
val paramStr = safeArgs.paramStr
Example: app_nav_graph
- ViewModel never keep context of view (fragment, activity)
- ViewModel init by owner scope (fragment, activity) and ViewModelFactory.
- Using LiveData to expose to Views (Fragment, Activity)
init 1 ViewModel in fragment:
1. Add DI to ViewModelModule
2.
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
3. in onViewCreated()
viewModel = getViewModel(viewModelFactory)
What is LiveData?
If we look at the definition form the Android website then it says:
LiveData is an observable data holder class. LiveData is lifecycle-aware,
meaning it respects the lifecycle of other app components, such as activities, fragments, or services.
This awareness ensures LiveData only updates app component observers that are in an active lifecycle state
1. MutableLiveData should be used only for notifying your UI when observing any data.
2. MediatorLiveData merge multi LiveData (by addSource and removeSource)
to understanding livedata: link
We have two ways to for set value for LiveData
1. livedata.value = xxx -> only set on mainthread.
2. livedata.postValue(xxx) -> can set in background thread.
in ViewModel:
var toHome = MutableLiveData<Boolean>()
fun toHome(){
toHome.value = true
}
and in Fragment
viewModel.toHome.observe(viewLifecycleOwner, Observer {
if(it){
}
})
ROOM in module: local
When we want to create 1 table we need to:
1. Create 1 file Entity
2. add class entity to AppDatabase
3. Create abstract class DAO
DAO object
return 2 type:
1. suspend function: always use ( I will explain suspend function in Coroutines)
2. normal function return type Flow: this is new in coroutines, I have not implement yet.
Example: UserGitDao
Retrofit in module: remote
Define api in class api.
Define function call api in DataSource
private fun getUsers(forceRefresh: Boolean) = viewModelScope.launch(dispatchers.main) {
withContext(dispatchers.io) {
usersSource = getTopUsersUseCase()
}
_users.addSource(usersSource) {
_users.value = it
}
}
1. Already add by Dagger
2. @Inject EventManager for post and receive
3. Define requestCode in MessaeEvent
with receiver: register in onCreate, unregister in onStop
@Subscribe(threadMode = ThreadMode.BACKGROUND)
fun onMessageEvent(event: MessageEvent) { /* Do something */
}
Can Subscribe with Main thread or Background.
Example With post:
val message = Message()
message.data = bundleOf(
Pair("arg1", "Arg1 From App Second Fragment"),
Pair("arg2", "Arg 2 From App Second Fragment")
)
eventManager.post(MessageEvent(MessageEvent.REQUEST_MOOD_RATING_DONE, message))
1. In ViewModel: using viewModelScope (viewModelScope.launch..)
2. In Activity, Fragment: using lifecycleScope (lifecycleScope.launch..)
3. Dispatchers already provide with DI
Dispatchers.IO: This Dispatcher is used to execute long-running and blocking I/O operations
Dispatchers.Main: Confines itself to the Main thread, and hence, only using one thread
Dispatchers.Default: Using CPU, core of device
Dispatchers.Unconfined: shouldn't normally be used in code.
launch (scope.launch): return Unit()
async (scope.async): return a Deferred<T>
File example Coroutines: CoroutinesUseCase.kt
Worker, WorkRequest, WorkManager, WorkStatus
1. Create subclass of Worker -> Result Sucess, Failure, Retry
2. Defines Contraints:
.setRequiresBatteryNotLow(true)
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresCharging(true)
.setRequiresStorageNotLow(true)
.setRequiresDeviceIdle(true)
3. Create WorkRequest
- OneTimeWorkRequest to run work only one time.
- PeriodicWorkRequest to run periodically, set the interval between to subsequent work. (min in 15 minutes)
4. Schedule the request
WorkManager.getInstance().enqueue(request)