Android

Guide

  • Click šŸ” Icons To Jump To Table of Contents
  • Topic With ā­ Icons are questions asked from me in Interviews, Click on it to jump to Interview Questions Section
  • Click šŸ’‰ before topics for detailed section of it.

Table of Contents

Topics

Android Platform Architecture

šŸ”

  • Linux Kernel : Core of android platform architecture. Manages all the hardware drivers, low-level memory.
  • Hardware Abstraction Layout (HAL) : Bridges hardware capabilities to the higher-level Java API Framework by defining standard interfaces.
  • Android Runtime (ART) : Converts Dalvik Executable Format (DEX) bytecode into machine code that system can understand. Replaced Dalvik Virtual Machine for devices running Android 5.0 (Lollipop).
  • Native C/C++ Libraries : Native APIs let you manage native activities and access physical device components like camera, sensor, graphic .
  • Java API Framework (Application Framework) : Collection of Android Libraries written in Java and Kotlin. Ex: Android Jetpack
  • System Apps : Pre-installed apps such as email, SMS messaging, calendars, contacts

Definitons

šŸ”

  • ā­ Context : It is the context of the current state of application/class. Used to get information regarding the activity and application. Used to access Resources, Databases, SharedPreferences
    • Application Context : Tied to lifecycle of an application, can be used to create singleton objects.
    • Activity Context : Tied to lifecycle of an activity, should be used when passing the context in activity.
  • ā­ Application Class : Base class within an Android app that contains all other components such as activities and services. It is instantiated before any other class when the process for your application is created.
  • Android Manifest : Describes essential information about the application such as package name, entry points, components, permissions, and metadata.
    • Package Name : Applicationā€™s univerally unique application ID

Anroid App Components

šŸ”ā­

App components are like entry points that allow systems and users to interact with your application. Each component have their own function and lifecycle.

  • šŸ’‰Activities : Entry point for interacting with users, represents single screen with UI
  • šŸ’‰Services : Entry point for keeping app running in background for all kinds of reason, like music player, youtube video player.
    • startService() : Allows other components to run a service in background or stop it, using startService() & stopService() respectively.
    • bindService() : Same as startService but also provides IBInder interface, which allows the client to communicate with the service consistently. Use unbindService to cancel the connection
  • Broadcast Receivers : Registerable listener that listens to broadcast messages from Android system or other applications. Where Broadcasts are used to send messages across apps, outside of the normal user flow, like device starts charging. No lifecycle like Services and Activities.
  • Content Providers : Manages shared set of data. Through content providers, apps can query or modify other appā€™s data if they have required permissions.

Intents

šŸ” Intent is a messaging object that is used to request an action from another app component.

  • Explicit Intents : Explicit Intents are used to start a specific component within the same application or another application by explicitly specifying the target componentā€™s class name.
  • Implicit Intents : Implicit Intents declares a general action to perform like showing gallery image, opening URL on web browser, you can use implicit intent to request action to the android system. Then android system shows all the appropiate components for that request if found.

Launch Modes

šŸ”

  • Standard : Default launch mode, creates new instance every time even if activity instance is already present
    A->B->C->D
    launch B1
    A->B->C->D->B(new instance)
    
  • Single Top : If instance of same activity exists on top pass the extras data though *onNewIntent(Intent intent), otherwise creates a new instance.
    A->B->C->D
    launch D
    A->B->C->D(old instance gets extra data)
    launch C
    A->B->C->D->C(new instance of C)
    
  • Single Task : Can only have one instance in the system (Singleton)
    A->B->C->D
    launch C
    A->B->C(old instance gets extra data) destroyed D
    
  • Single Instance :
    A->B->C->D
    launch E
    A->B->C->D | E
    launch F from E
    E | A->B->C->D->F
    

Architecture Components

šŸ”

Android Jetpack

šŸ”

Suites of libraries to reduce boiler plate code, follow best practices.

  • App Architecture : Googleā€™s recommneded app achitecture below allows apps to scale, improve quality, robustness and makes apps easier to test.

    UI Layer -> Domain Layer -> Data Layer

    • UI Layer : Render the updated application data on screen. Also known as Presentation layer. Made of UI elements, and State holders, such as ViewModel
    • Domain Layer : Abstracting complex/simple business logic, converts complex data into suitable types for UI layers. Optional
    • Data Layer : Contains the business logic. Made of repositories, that can contain zero to many data sources.
  • UI Layer Libraries
    • ViewBinding : Generates a binding class for each XML layout file.
    • DataBinding : Generates a binding class for XML layouts that includes a layout tag. Linkd View and ViewModel with observer pattern, properties and event callbacks.
    • šŸ’‰ ViewModel : Itā€™s a state holder to store and manage UI related data surviving configuration changes.
    • šŸ’‰ LiveData : Itā€™s a lifecycle-aware data holder, which can be observed by multiple Observers, used to implement Observer Pattern.
    • Lifecycle : Jetpackā€™s Lifecycle allows you to build independent components, which observes the lifecycle changes of lifecycle owners like activities or fragments.
    • šŸ’‰ Fragment : segment your app into multiple, independent screens that are hosted within an Activity.
    • šŸ’‰ Compose
  • Data Layer Libraries
    • DataStore : Used to store lightweight key-value pairs in local storage, works with Coroutines and Flow to store data asynchonously. Can be used to replace SharedPreferences.
    • Room : Abstraction layer over SQLite Databases simplyfying access of database.
    • šŸ’‰WorkManager : Background Processing API, gurantees background work by scheduling works, runs deferrable.
    • Paging :

Creational Design Patterns

šŸ”

Reusable solutions to solve repeated and common software problems in software engineering. These are lower-level patterns used to address object creation and manage dependencies within your code. They focus on how objects are created, configured, and returned.

  • Singleton Pattern: Only one instance exists in the application
    object Singleton{ 
      fun doSomething(){
    
      }
    }
    
  • Factory Pattern: Where factory takes care of all the object creational logic. In this pattern, factory class controls which object to instantiate. This pattern comes in handy when dealing with many common objects.
  • Builder Pattern: Builder pattern in android is used to construct complex objects with optional parameters. It provides clean and fluent API for creating objects step by step. Ex: AlertDialog.Builder(), Intent.Builder(), Room, Retrofit
    class Hamburger private constructor(
        val cheese: Boolean,
        val onions: Boolean
    ) {
        class Builder {
            private var cheese: Boolean = true
            private var onions: Boolean = true
    
            fun cheese(value: Boolean) = apply { cheese = value }
            fun onions(value: Boolean) = apply { onions = value }
    
            fun build() = Hamburger(cheese, onions)
        }
    }
    
    //use as 
    Hamburger ham = Hamburger.Builder().cheese(true).onions(false).build()
    

    Can be replaced with data classes in kotlin

    var ham = Hamburger(cheese = true, onions = false)
    
  • Facade Pattern: Facade (surface) pattern is a structural design pattern that provides higher-level interface that make set of other interfaces easier to use. Example Retrofitā€™s
    interface Movies {
      @GET("movies")
      fun getMovies(): List<Movies>
    }
    
  • Dependency Injection : When dependencies are provided to a class instead of creating them itself. Advantages of DI are reduces boilerplate code, loose coupling between classes, hence easy testing, code reuability, code maintainability.
    • Hilt : Compile time dependency injection tool which works on top of Dagger, reduces the boilerplate code compared to Dagger.
    • Dagger : Also compile time dependency injection tool based on javax.inject annotation.
    • Koin : Popular dependency injection tool for Kotlin projects.

  • Observer Pattern : It is a behavioral design pattern that allows you to build a subscription mechanism to notify observers automatically of any state changes.
    • šŸ’‰ LiveData : Lifecycle-aware, thread-safe and data-holder observer pattern. Bounds to lifecycle, so we donā€™t need to unsubscribe observers manually, prevents memory leaks, but with adoption of coroutines broadly, Kotlin Flows are more preferred now.
    • šŸ’‰ Kotlin Flows : Asynchronous solution that is cold streams similar to sequences working with Coroutines. They are asynchronous and non-blocking solutions.
    • RxKotlin : Originated from ReactiveX, which is combination of observer pattern, iterator pattern, functional programming.
  • Repository Pattern :

Architectural Pattern

šŸ”

Architectural Patterns are system-wide and deal with the overall structure of the application, such as Clean Architecture.

Architecture defines boundaries between each layer, defines the responsibilities clearly affecting projectā€™s complexity, scalability and robustness, and makes it easier to test.

Clean Architecture

  • Clean architecture is about organizing code into layers to ensure that the core business logic is independent of frameworks, UI, and external data sources.
  • In android it constitutes of three layers:
    • Presentation Layer: Architectural Design Patterns like MVVM and MVI focus on structuring the presentation layer, handling UI interactions, and managing state.
      • View : Its main responsibility is to display data to the user and respond to user interactions.
      • ViewModel : It holds and manages UI-related data. While it interacts with Use Cases to request or modify data, it does not perform business logic.
    • Domain Layer:
      • Entities: Core business objects that are independent of UI, databases, or any frameworks.
      • UseCase: This is where the business logic resides. A Use Case is responsible for performing specific operations, such as fetching a list of movies, processing data, or applying any necessary transformations.
    • Data Layer: These represent how the data is presented and consumed. Repositories and mappers live here, converting data from external sources (APIs, databases) into entities.
  • Flow:
    1. The UI layer (View and ViewModel) interacts with the Domain layer (Use Cases) and asks for data.
    2. The Domain layer is independent of any Android-specific code, making it highly testable.
    3. The Data layer (Repositories) fetches data from external sources like databases or APIs and provides it to the Domain layer.
  • Dependency Rule::
    1. Each layer should only depend on abstractions, not on concrete implementations.
    2. The outer layers (UI and Data) can depend on the inner layers (Domain), but the Domain layer should not depend on the UI or Data layers.

Architectural Design Pattern

šŸ”

Architectural Design Patterns like MVVM and MVI focus on structuring the presentation layer (of Clean Architecture), handling UI interactions, and managing state.

MVC

Stands for Model, View, Controller.

  • Model : It is the business logic and data state. Used to retieve and manipulate data, communicate with controllers, interact with database and update views.
  • View : View determines what the user sees in an application, XML.
  • Controller : Itā€™s Activity/Fragment i.e. communitcation betwween Model and View. Handls user interactions with our application.

User interacts with the UI, which notifies the Controller. Based on the interaction the Controller modifies particular models, then Models perform some business logic and return the updated model state to Controller, which updates the UI according to the new data state.

figure

MVP

Stands for Model, View, Presenter.

  • Model : Layer for storing data, handles domain/business logic and is responsible for communicating with database and netwrok layers.
  • View : UI layer i.e. Views/Layouts/Activities/Fragments. Will implement as interface for the Presenterā€™s actions.
  • Presenter : Presenter has no relation with Views (unlike MVC). Operations are invoked by our views, and View are updated via Viewā€™s interface.

In MVP, Views and Presenters interact each other via interface (unlike MVC). Presenters perform some action on the Interface, which is implemented in Views, which results in updation of View

figure

MVVM

ā­

One of the most popular achitecture designs in modern Android Development since Google officially announced Architecture Components, such as ViewModel, LiveData and Data Binding. Consists of View, ViewModel, Model

  • View : Responsible for constructing the UI of what the user sees on the screen, includes UI elements such as TextView, Button, or Jetpack Compose UI. UI elements trigger user events to the ViewModel and configure UI screens by observing data or UI states from the ViewModel. Includes only UI logic and not business logic.
  • šŸ’‰ ViewModel : Independent component that does not have any dependencies on View, holds buisness logic or UI states from the Model to propogates them into UI elements. ViewModel notifies data changes to View as domain data or UI states.
  • Model : Encapsulates the appā€™s domain/data model, which typically includes buiness logic, complex computational works.

MVI

MVI is another design pattern but focuses more on a unidirectional data flow and treating state as immutable. Itā€™s a bit more modern and reactive than MVVM and fits well with frameworks like Jetpack Compose

  • Model : Represents the current state of the screen or UI. In MVI, this state is immutable and a single source of truth for the entire UI.
  • View : This is similar to MVVM, where the View renders UI, but it reflects the current state provided by the Model.
  • Intent : User actions or events from the View trigger Intents which are passed to the ViewModel. These Intents are then processed, resulting in a new state.

Design Principles

šŸ”

Design principles are a set of guidelines or best practices for writing clean, maintainable, and efficient code. These principles help in organizing and structuring code to make it easy to understand, flexible for future changes, and less prone to errors.

SOLID

SOLID is a set of five principles for designing maintainable, flexible, and scalable object-oriented software. These principles guide software development to ensure that systems are easy to understand, extend, and modify without causing major code breakage.

  • Single Responsibility Principle : Each class should focus on doing one thing well. If a class has more than one responsibility, changes to one part of the class can impact the other part, making the system harder to maintain.
  • Open/Closed Principle : Software entities (classes, modules, functions) should be open for extension but closed for modification. : You should be able to add new functionality to a class or module without changing its existing code. This is typically done using abstraction and inheritance or interfaces.
  • Liskov Substitution Principle :
  • Interface Segregation Principle : No client should be forced to depend on interfaces it does not use : Instead of having one large interface that covers all use cases, itā€™s better to create smaller, more specific interfaces. This way, classes only need to implement methods they actually use.
  • Dependency Inversion Principle : High-level modules should not depend on low-level modules. Both should depend on abstractions. This reduces coupling between components and makes the system more flexible and easier to modify.

DRY

KISS

DI

Brief

Services

šŸ”

A service is an application component that can perform long-running operations in the background. Moreover, main android components can bind to service to interact with it and also can perfrom InterProcess Communication (IPC)

  • For ex: Service to handle netwrok transactions, play music, perform I/O, or interact with content provider, all from backrgound.
  • Caution : A service runs in the main thread, it neither creates its own thread nor run in a seperate process unless you specify otherwise. You should run any blocking operations on a seperate thread within the service to avoid Application Not Responding (ANR) erros.
  • Types of Services
    • Foreground : Service which performs some operations that is noticeable to the user like playing audio track. Must display a notification, so that users are actively aware that service is running. This notification cannot be dismissed unless service is either stopped of removed from the foreground. It continues even when the user isnā€™t interacting with the app. WorkManager API offers flexible and nearly same ways as foreground services too.
    • Backgound : Service which performs an operation that isnā€™t directly notified by the user, e.g. background service to compact its storage. System imposes restrictions on API 26 or higher from running background services, when the app itself isnā€™t in the foreground.
    • Bound : Type of service that offers a client-server interface that allows components(Activity, content provider and service can bind to the Bound service) to interact with the service, send requests, receive results, and even do so across processes with IPC. Bound service runs only as long as another application component is bound ot it. Multiple Components can bind to service at once, but when all of them unbind, the service is destroyed. Ex: Music Player service.
  • Services v/s Threads : Service is simply a component that can run in the background, even when the user is not interacting with the application, whereas, if you must perform work outside of your main thread, but only while the user is interacting with your application, you should create a new thread. For example : Use service to play audio even if application is in background, and use Thread to play some video but only while the activity is running, you might create a thread in onCreate(), start running in onStart() and stop in onStop()

Activities

šŸ” Activities is an independent and reusable component that interacts with the user by providing UI-relevant resources.

  • Activity Lifecycle Figure
    onCreate() -> onStart() -> onRestoreInstanceState -> onResume() -> onPause() -> onStop() -> onDestroy()
    • onCreate() : This callback is called when the system creates your activity. Includes initilization logic, which should occur only once like creating views or binding data
    • onStart() : This callback is called when the activity becomes visible to the user.
    • onRestoreInstanceState() : Used to recover the saved state of an activity during recreation, through Bundles.
    • onResume() : Means activity is ready to come to foreground and interact with users. Initialize components like camera, video player
    • onPause() : Means activity is no longer in the foreground, and may still be partially visible. Release components like camera, video player
    • onStop() : This callback is called when the activity is no longer visible to the user. Shutdown CPU intensive operations like maybe saving data in ROOM
    • onSaveInstanceState() : Used to store data before pausing the activity.
    • onDestory() : This callback is called before and activity is destroyed. Release all remaining resources here
  • LifeCycle Scenarios
    • Navigate A to B :
      onPause(A) -> onCreate(B) -> onStart(B) -> onResume(B) -> onStop(A)
    • Navigate back to A from B :
      onPause(B) -> onRestart(A) -> onStart(A) -> onResume(A) -> onStop(B) -> onDestory(B)
    • Press HomeButton\ScreenLock From A :
      onPause() -> onStop() -> onSaveInstanceState()
    • Opening From ScreenLock\HomeScreen :
      onRestart() -> onStart() -> onResume()
    • Configuration Change :
      onPause() -> onStop() -> onSaveInstanceState() -> onDestroy() -> onCreate(bundle) -> onStart() -> onRestoreInstanceState() -> onResume()
    • Destroying App :
      onPause(A) -> onStop(A) -> onDestroy(A)
    • Calling *finish()* :
      onDestroy(A)

ref: The Android Lifecycle cheat sheet

Fragments

šŸ”

Reusable part of UI that interacts with users by providing UI elements on top of activities. Managed by Fragment Managers.

  • ā­Fragment Lifecycle Figure onAttach() - > onCreate() -> onCreateView() -> onActivityCreated() -> onStart() -> onResume() -> onPause() -> onStop() -> onDestroyView() -> onDestroy() -> onDetach()
    • onAttach() : This method is called first to know that fragment has been attached to the Activity.
    • onCreate() : This method is called when the fragment instance initializes.
    • onCreateView() : This callback is called when frgament is ready to draw its UI for first time. To draw UI, return the fragmentā€™s root layout view component, return null if no UI.
    • onActivityCreated() : This callback is called when Activity completes its onCreate() method.
    • onStart() : Means fragment is visible.
    • onResume() : Means fragment is visible and ready to interact with users.
    • onPause() : This method is called when a fragment is not allowing the user to interact.
    • onStop() : This callback is called when the fragment is no longer visible to the user.
    • onDestroyView() : This method is called when the view and realted resources created in onCreateView() are removed from activityā€™s view hierarchy & destroyed.
    • onDestory() : This callback is called before and activity is destroyed.
    • onDetach() : When fragment is detached from its host activity.
  • ā­ add vs replace : replace removes the existing fragment and adds a new fragment, means when you press back button the fragment that got replaced will be recreated with its onCreateView() being invoked, wheres add retains the existing fragments and adds a new fragments means existing fragment will be active, wont be in paused state.

ViewModel

šŸ”

ViewModel is class designed to hold and manage UI-related data in a life-cycle consious way. This allows data to survive configuration changes such a screen rotations.

  • ViewModel exists from when you first request a ViewModel (usually onCreate) untill the activity is finished and destroyed.
  • Extend AndroidViewModel if you need context inside viewModel, which will provide Application Context.
  • ViewModelProviders.of method creates a new ViewModel instance when itā€™s called first time. When itā€™s called again, which happens whenever onCreate is called, it will return the pre-existing ViewModel associated with the specific Activity/Fragment. This preserves the data.
  • Advantages :
    • Handle Configuration changes
    • Lifecycle Awareness
    • Data Sharing
    • Kotlin Coroutines Support
  • ā­ How ViewModels Work Internally :

    Hereā€™s code to get the instance of viewModel :-

    viewModel = ViewModelProvider(this, ViewModelFactory()).get(SampleViewModel::class.java)
    

    We get the instance of ViewModelProvider by passing instance of the Activity and ViewModelFactory, then we are using that ViewModelProvider instance to get the SampleViewModel object. Now, ViewModelProvider internal codes looks like:-

    public open classViewModelProvider constructor(
      private val store: ViewModelStore,
      private val factory: Factory,
      private val defaultCreationExtras: CreationExtras = CreationExtras.Empty
      ) {
        ..
    }
    

    Where ViewModelStore internal code looks like:-

    public class ViewModelStore {
      private final HashMap<String, ViewModel> mMap = new HashMap<>();
      final void put(String key, ViewModel vm) {..}
      final ViewModel get(String key) {..}
      ..
    }
    

    At the lowest, the object creation of ViewModel is handled by ViewModelStore, which contains HashMap<String, ViewModel>

    where, Key Format : val canonicalName = modelClass.canonicalName.

    Hence ViewModelStore checks if key for ViewModel exists in the HashMap.

    • If yes, return the already existing object.
    • If no, create a new ViewModel, and store the object in HashMap for future usage.

Coroutines

šŸ”

Coroutines are powerful feature introduced in Kotlin to handle asynchronous programming in a more concise and efficient manner. In context of Android development, coroutines provide a way to perform asynchronous operations, such as network requests, database queries, without blocking the main thread.

  • Advantages of coroutines:
    • Main thread safety: Coroutines allows developers to perform asynchronous tasks without blocking the main thread.
    • Simplified asynchronous code: Coroutines provide a more concise and readable way to write asynchronous code compared to traditional callback-based approaches. This leads to code that is easier to understand, maintain, and debug.
    • Integration with Lifecycles: Coroutines can be seamlessly integrated with the Android components lifecycle like Activity or fragment, helping to avoid memory leaks and waste of resources,
  • suspend : Keyword to mark a function available to coroutines, suspends exceution until the result is ready then it resumes where it left off with the result. Using suspend doesnā€™t tell Kotlin to run a function on a background thread.
  • Dispatchers : Context
    • Dispatchers.Main : Lightweight tasks eg - network calls, database queries, wonā€™t block the main thread while suspended. Common when you need to perform UI-related tasks or update the user interface from coroutines.
    • Dispatchers.IO : For heavy IO work such as reading from or writing to files, making network requests, or interacting with databases.
    • Dispatchers.Default : For CPU intensive tasks. Itā€™s suitable for computational work or network requests that donā€™t require interaction with the UI.
    • Dispatchers.Unconfined : Runs coroutines unconfined on no specific thread, not recommended to use.
  • CoroutineScopes : Keeps track of coroutines, even suspended ones, can cancel all of the coroutines started in it.
    • globalScope : Top level Coroutine scope that will be associated with the application lifecycle. Since itā€™s alive along the application lifetime, itā€™s a Singleton object
    • viewModelScope : Coroutine scopre tied to viewModel. Extension function of the viewModel class, bound to Dispatchers.Main and will automatically be cancelled when viewModel is cleared.
    • lifecycleScope : Bound to lifecycle of the Activity/Fragment to avoid memory leak and resource waste.

      Q: What if we want to cancel only some coroutines and retain some other coroutines inside the scope?
      Ans: We can define Job using launch and cancel it whenever we want. Job is actually coroutine itself.

    private lateinit var coroutineScope: CoroutineScope
    private lateinit var job1: Job
    private lateinit var job2: Job
    
    private fun startCoroutine(){
      coroutineScope = CoroutineScope(Dispatchers.Main)
      job1 = coroutineScope.launch {  println("job1") }
      job2 = coroutineScope.launch {  println("job2") }
    }
    
    private fun cancel() {
      if (::mJob1.isInitialized && mJob1.isActive) { 
        mJob1.cancel() 
        }
    }
    
  • Room and Retrofit make suspending functions main-safe, itā€™s safe to call these suspend functions from Dispathers.Main, even though they fetch from network and write to database. Do not use Dispatchers.IO.
  • Builders :
    • runBlocking{} : Is used to create or launch a new coroutine that blocks the current thread until all its child coroutines complete aka stop the excution of code written after it
    • launch{} : Will start a new coroutine without blocking the current thread that is fire and forget - that means it wonā€™t return the result to the caller. Instead, it returns an object of type Job, which can be used to cancel the coroutine and also includes a function named join(). Like await(), the join() function suspends the coroutine until the code in the launch() block has completed.
    • async{} : This builder works a lot like launch(), but instead of returning a Job object, it returns an object that is a subtype of Job, named Deferred. This object gives us a function named await(), which allows us to get the result from order(). await() is a suspending function, and it will suspend the coroutine until its async() coroutine has completed.
    • coroutineScope : Is used to create a new coroutine scope and wait for all its child coroutines to complete. Itā€™s similar to runBlocking but doesnā€™t block the thread itā€™s called on
    • withContext{} : Is used to switch the coroutineā€™s context temporarily. Itā€™s useful for changing the thread or coroutine dispatcher within a coroutine. This builder allows you to execute code in a different context without starting a new coroutine.
      withContext(Dispatcher.Main) { }
      
  • Structured Concurrency : A parent coroutine scope having children coroutine scopes. It gurantees that when a suspend function returns, all of its work is done and also when a coroutine errors, its caller or scope is notified.
    suspend fun callTwoAPI(){
      coroutineScope{
        async { callAPI(1) }
        async { callAPI(2) }
      }
    }
    
    • CoroutineScope : Coroutines launched within a CoroutineScope are automatically cancelled when the associated lifecycle component is destroyed, helping to manage the lifecycle of coroutines.
    • coroutineScope : coroutineScope builder will suspend itself until all coroutines started inside of it are complete, hence thereā€™s no way to return from callTwoAPI until all coroutines are completed. Cancels whenever any of the children coroutine fails meaning if one network request fails, all of the others request would be cancelled immediately.
    • supervisorScope : supervisorScope builder wonā€™t cancel children when one them fails.
  • Cancellation : using function cancel() we can cancel any coroutine.
    • Thanks to structured concurrency, when the root coroutine is canceled, we donā€™t need to manually cancel each of its children. Instead, each child coroutine is automatically sent a signal to cancel.
    • But if coroutine code doesnā€™t cooperate by choosing to check for cancellation, it wonā€™t notice the signal itself. We can check the isActive property of the CoroutineScope, or by calling its ensureActive() function.
    • Cancelling child coroutine : We can cancel any child coroutine by calling cancel(), the cancellation does not affect parent or sibling coroutines.

ā­ Q: What is a difference between Threads & Coroutines?
Ans: Threads are expensive, require context switches which are costly, and number of threads that can be launched is limited by the underlying operating system whereas, Coroutines can be thought of as light-weight threads, means the creating of coroutines doesnā€™t allocate new thread, instead they use predefined thread pools and smart scheduling for the purpose of which task to execute next and which tasks later.

Flow

šŸ”

Flow is an asynchronous data stream that emits values to the collector and gets completed with or without an exception.

  • Types of data streams:
    • Hot Stream : Hot streams trasmit data even if thereā€™s no subscriber when the data arrives. Ex: Channels, for fetching data from APIs, however since there will always be data flow in hot stream, it is necessary to keep the subscribers under control. Because it can cause data loses(if subscriber is forgotten) and memory leaks(open network cinnection)
    • Cold Stream : Cold streams trasmit data only when you start collecting. Ex: Kotlin Flow, powered by Kotlin Coroutines, and because of Kotlin Coroutines, when you cancel the scope, you also dispose of the flow. We donā€™t have to free up memory manually.
  • Major Components of Flow:
    • (a) Builders:
      • flow{} : create a flow from the suspendable lambda block.
        flow {
          (1..5).forEach{
            emit(it)
          }
        }.collect{
          print(it)
        }
        
      • flowOf() : to create a flow from fixed set of values
        flowOf(1,2,3,4,5)
          .collect {
            print(it)
          }
        
      • asFlow() :
        (1,2,3,4,5).asFlow()
        .collect {
          print(it)
        }
        
      • channelFlow() :
        channelFlow {
          (0..10).forEach{
            send(it)
          }
        }.collect{
          print(it)
        }
        
    • (b) Operators: The operator helps in transforming the data from one format to another.
      • Intermediate Operator : Used to modifying the data flows between the producer and consumer -OR- These operators are functions that, when applied to a stream of data, set up a chain of operations that arenā€™t executed until the values are consumed in the future and are executed lazily
        fun main() = runBlocking { 
          requestNumbers()
              .filter { number -> number > 2 }
              .map { number -> toUiModel(number) }
              .catch { error -> println(error) }
              .collect { println(it) } 
        }
        

        Upstream flow : Flow produced by the operators that are called before the current operator whereas Downstream flow is Flow produced by operators that are called after the current operator.

        • buffer() : Normanlly, flows are sequential, that means the code of all the operators is executed in the same coroutine. Which means the total execution time is going to be the sum of execution times of all operators. Hence buffer creates a seperate coroutine during execution for the flow it applies to.
        • zip() : Zips the emissions of two flow emissions with a specified function and emits single item for each combination. Example: Merging two parallely running tasks and getting the results of both task in single callback when both are completed.
            fun runningTaskOne = Flow<String> { 
              return flow {
                delay(2000)
                emit(one) 
              }
            }
            fun runningTaskTwo = flow {
              return flow {
                delay(2000)
                emit(two)
              }
            }
          
            fun startTask() {
              viewModelScope.launch {
                longRunningTaskOne()
                  .zip(longRunningTaskTwo()) { resultOne resultTwo ->
                    return@zip resultOne+resultTwo
                  }
                  .flowOn(Dispatchers.Default)
                  .collect{
          
                  }
              }
            }
          
      • Terminal Operator : Terminal operators are either suspending function such as collect, single, reduce, toList etc or launchIn operator that starts collection of the flow in the given scope. They are applied to the upstream flow and triggers execution of all operations. Execution of the flow is also called collecting the flow and is always performed in a suspending manner without actual blocking.
        • (c) Collector : Collect all the values in the stream as theyā€™re emitted. Is a suspend function and needs to be executed within a coroutine. It takes a lambda as a parameter that is called on every new value. Since itā€™s a suspend function, the coroutine that calls collect may suspend until the flow is closed.
        • count : Count the values that matches specific conditions.
        • reduce : Apply a function to each item emitted and emit the final value
          val result = (1..5).asFlow()
                      .reduce {a,b -> a+b}
          print(result) 
          //15
          
        • fold :
            val count = flow.count{
          
            }
            val reduce= flow.reduce{accumulator, value->
          
            }
          
  • flowOn() : Used to controlling the thread on which the task will be done.
    val flow = flow {
      (1..5).forEach {
        emit(it)
      }
    }.flowOn(Dispatchers.Default)
    
    CoroutineScope(Dispatchers.Main).launch{
      flow.collect {
        print(it)
      }
    }
    
  • Cold Flow v/s Hot Flow : Cold Flow only emits data when thereā€™s a collector, canā€™t have multiple collectors and also canā€™t store data whereas Hot Flow emits data even when there are no collectors attached to it, it can have multiple collectors and also can store data.
    fun getColdFlow(): ColdFlow<T> {}
    fun getHotFlow(): HotFlow<T> {}
    
  • State Flow v/s Shared Flow : Both flow types are examples of Hot Flow.
    • State Flow : StateFlow needs an initial value and emits it as soon as collector starts collecting. It has the value property to check the current value, but also keeps the history of one value that we can fetch directly without collecting. StateFlow emits only distinctive values, and ignores repeated ones. It is similar to LiveData but without awareness of Android components lifecycle, repeatOnLifecycle scope can be used to add the lifecycle awareness to StateFlow. Ex: To store and show data from network calls so it retains value if orientation changes resulting in prohibiting the network call again.
    • SharedFlow : SharedFlow doesnā€™t needs an initial value, so does not emit any value by default, neither it supports value property. It can emit previous values too using replay operator. SharedFlow also emits all the value including the repeated ones too. Ex: Show snackbars so it doesnt retains value if orientation changes and snackbar doesnā€™t shows again.
      val stateFlow = MutableStateFlow(0)
      val sharedFlow = MutableSharedFlow<Int>()
      
  • Generally flow refresents cold streams but thereā€™s a hot stream subtype i.e. SharedFlow, also any flow can be turned into hot one by stateIn or shareIn operators, or by converting the flow into a hot channel via produceIn operator.
  • Difference between launchIn and collect: Use collect when you want to collect and process Flow emissions within a coroutine. Use launchIn when you want to launch a new coroutine to collect Flow emissions and manage its lifecycle separately.

ref: Mastering Flow API
ref
ref

Dependency Injection

šŸ”

  • Dependency : Object which is to be used by a dependent i.e. class
  • Injection : Technique which passes the dependency to dependent i.e. object to the class which wants to use it.
  • Dependency Injection : Technique where dependencies are provided to a class instead of creating them itself.
  • DI helps in laying the groundwork for good app architecture, greater code reuability, and ease of testing.

Hilt

DI framework build on top of Dagger, brings benefits like compile time correctness, runtime performance, scalability that Dagger provides, but also Hilt is integrated with Jetpack libraries and removes most of the boilerplate code to let us focus on just the important parts.

  • Hilt Automatically generates:
    • Components for integrating Android framework classes, in Dagger we have to do it manually.
    • Scope Annotations for the components.
    • Predefined bindings and qualifiers.
  • Annotations
    • @HiltAndroidApp : Kicks off Hilt code generation. For Application class.
    • @AndroidEntryPoint : Add DI container to Android class. For Classes
    • @Inject :
      • Constructor Injection : Tells which constructor to use to provide instances and which dependencies it has
        @AndroidEntryPoint
        class SomeAdapter @Inject constructor(private val service: SomeService)
        {..}
        
      • Field Injection : Populates fields in @AndroidEntryPoint classes.
        @AndroidEntryPoint
        class MainActivity: AppCompatActivity(){
          @Inject lateint var adapter: SomeAdapter
          ...
        }
        
    • @HiltViewModel : Tell hilt how to provide instances of ViewModel.
    • @Module : Class in which you can add bindings for types that cannot be constructor injected.
    • @InstallIn : Indicates in which Hilt generated DI container module bindings must be available
    • @Provides : Adds binding for a type that cannot be constructor injected. Like ROOM instance, Retrofit Instance.
        @InstallIn(SingletonComponent::class)
        @Module
        class SomeModule {
          @Provides
          fun providesRetrofitInstance(..): RetrofitInstance {..}
        }
      
    • @Binds : Shorthand for binding an interface type
    • @Singleton/@ActivityScoped : Scoping object to container. The same instance of a type will be provided by container when using that type as a dependency.

RecyclerView

šŸ”

  • A ViewGroup to efficiently display large sets of data. You supply data, and define how each item looks, and RecyclerView library dynamically creates the elements when theyā€™re needed.
  • Components of RecyclerView
    • Adapter : Takes data and sets the data for views
      • onCreateViewHolder() : To create a new ViewHolder, not filled with data.
      • onBindViewHolder() : To associate a ViewHolder with data.
      • getItemCount() : To get the size of the data set.
    • ViewHolder : Wrapper around a View that contains layout of and individual item.
    • LayoutManager : Manages how we need to display the items on screen.
  • ā­ Difference between ListView & RecyclerView?
    • ViewHolderPattern : In ListView it was a recommended pattern but not compulsion like in RecylerView.
    • LayoutManager : ListView only supported vertical ListView, not even horizontal ListViews. While recyclerView has
      • LinearLayoutManager : Both Vertical and Horizontal lists.
      • GridLayoutManager : Displaying items in grid like gallery.
      • StaggeredLayoutManager : Like Pinterest.
    • Performance : As name implies RecyclerView recycles the individual elements. When an item scrolls off the screen, it doesnā€™t destroy its view. Instead, it reuses the view for new items that have scrolled onscreen. This resue vastly improves performance, appā€™s responsiveness and reduces power consumption.
    • Animations & Decorations : ListView lacks in support of good animations and decorations, but ItemAnimator and ItemDecorator classes of RecyclerView provides developers huge control over these things.
  • Internal Working Of RecyclerView
    • RecyclerView loads view just ahead and behind the visible entries. So, the Scrapped View (View which was once visible and now not visible) gets stored in a collection of scrapped views. Now as we keep scrolling, the view from that collection is used. The view which we loaded from the scrapped view is called Dirty view. Now. the dirty view gets recycled and is relocated as the new item in queue which has to be displayed on the screen.

      Q. How to optimize RecyclerView?
      Ans:

WorkManager

šŸ”

WorkManager aims to simplify the developer experience by providing a first-class API for system-driven background processing. It is intended for background jobs that should run even if the app is no longer in the foreground. Where possible, it uses JobScheduler or Firebase JobDispatcher to do the work; if your app is in the foreground, it will even try to do the work directly in your process.

  • Components

Thread

šŸ” It is a lightweight process that an operating system can schedule and run concurrently

  • Types of thread:
    • OS Thread: Based on OS concept, A thread is a basic unit of CPU utilization. It is comprises of Registers, Program Counter, ThreadID and a Stack. They are also known as Kernel Thread, as they are controlled by Kernel.
    • User Thread: Threads that are controlled by users, but in order for the kernel to process the operations in a user thread, each user thread should be passed into the kernel after they are created.
  • User Thread in Java: In java, user thread is defined as ThreadClass, which is an implementation of Runnable.
  • Why do we need thread?: Android application runs on a single thread by default, called the Main or UI thread, which is responsible for UI interactions and drawing on screen. However performing long, heavy tasks on this thread might lead to ANR, unresponsivenss, bad user interaction, hence we creatye background thread to offload heavy tasks.

  • Accessing UI from background/worker thread: As Android UI toolkit is not thread-safe, we should manipulate UI always from UI/Main thread and not from any background thread. But in case we need to access the UI threads from other threads we can use
    • Activity.runOnUiThread(Runnable)
    • View.post(Runnable)
    • View.postDelayed(Runnable, long)
  • How to create a thread?:
    • Extend Thread class
    • Implement a Runnable and pass it into a Thread object.
  • What are Thread and Runnable?: Thread is a class that implements Runnable, which is a single method interface that has an abstract function called run()

  • Thread Lifecycle:
    • NEW: state when thread is created
    • RUNNABLE: state when thread execute RUNNABLE, by calling THREAD#start()
    • BLOCKED
    • WAITING: state when current thread is waiting for result from anothe thread, can be achieved by calling wait(), join(), park()
    • TIMED_WAITING: similar to waiting, but will wait only for certain amount of time, can be achieved by sleep(time), wait(timeout), join(timeout), parnkNanos(), parkUntil()
    • TERMINATED: state when thread is completed or interrupted

Compose

šŸ”

Jetpack compose is a modern toolkit for building native Android UI. Compose simplifies and accelerates UI development on Android with less code, powerful tools and inuitive Kotlin APIs

  • Key Terms :
    • Composition : Description of UI built by Compose when it executes composables.
    • Initial Composition : When composables gets drawn for the first time.
    • Recomposition : When composables (and their children) gets redrawn automatically when the value is updated. The only way to modify the Composition is through recomposition.
    • State : State or MutableState are interfaces that can hold some value and trigger UI updates whenever that value changes.
  • Annotations :
    • @Composable: Composable funtions are the functions where you define appā€™s UI programmatically by describing how it should look and providing data dependencies.
    • @Preview: Annotation to preview the composable functions within Android Studio. Not applicable for composable functions which does not take in parameters.
  • Modifiers : Modifier tell a UI elemnet how to lay out, display, or behave within its parent layout, add high-level interactions, such as making element clickable. As a best practice, your function should include a Modifier parameter that is assigned an empty Modifier by default.
    • ContentPadding : To maintain the same padding, but still scroll your content within the bounds of your parent list without clipping it, all lists provide a parameter called contentPadding
  • Elements
    • Text :
    • Surface : Allows the customizing like shape and elevation of items.
    • Layouts Elements :
      • Column : Arrange items vertically.
      • Row : Arrange items horizontally.
      • Box : Stack elements
    • LazyColumn/LazyRow : Composables that renders only the elements that are visible on screen, so they are designed to be very efficient for long lists. *Doesnā€™t reyce its children like RecycleView, instead emits new composables as you scroll though it and is still performat, as emitting composables is relatively cheap compared to instantiating Views.
    • Slots : Slot-based layouts leave an empty space in the UI for the developer to fill as they wish. You can use them to create more flexible layouts.
  • APIs :
    • remember : To preserve state across recompositions. Can store both mutable & immutable objects.
    • remeberSavable : same as remember but also stores them in bundle hence survives configuration changes. Store primitive types automatically like Int, String, boolean but custom objects/data classes needs to be parecelized.
    • mutableStateOf : creates an observable MutableState
    • collectAsState : Collects value from StateFlow and represents its latest value via State.
      StateFlow.value is used as an initial value, and everytime a new value is posted into the StateFlow, the returned State updates, causing recomposition of every State.value usage.
      var value by remember { mutableStateOf(default) }
      
  • State hoisting(lift/elevate) : State that is read or modified by multiple functions should live in common ancestor. This avoids duplicating state, bugs, helps resue composables, easier for testing.
  • Unidirectional Data Flow (UDF) : Deisng pattern in which state flows down and events flow up. By using UDF, we can decouple composables that display state in the UI from the parts of your app that store and change state.
  • Jetpack Compose supports other observables types also. But before reading another observable type in Jetpack COmpose, you must convert it to a State<T> so that Compose can automatically recompose when the state changes.
    • Flow : collectAsStateWithLifecycle() collects value from a flow in lifecycle-aware manner, allowing app to save unneeded app resources and tranforms it into Compose State. (collectAsStateWithLifecycle() uses *repeatOnLifecycle API under the hood, which is the recommended way to collect flows in Andorid using the View system.)
    • Flow : collectAsState() similar to collectAsStateWithLifecycle(). Use collectAsState for platform-agnostic code instead of collectAsStateWithLifecycle(), which is android-only.
    • LiveData: observeAsState() starts observing the LiveData and represents its values via State.
    • RxJava2 : subscribeAsState() are extension functions that transform RxJava2ā€™s reactive streams into Compose State.
    • RxJava3 : subscribeAsState() same as above.

Differences

  • ListView vs RecyclerView

Interview Questions

  • ListView vs RecyclerView
  • LiveData vs Flow
  • lazy vs lateinit
  • add() vs replace()
  • Are object variables thread safe
  • Inner working of ViewModel
  • Inner working of Extension Functions
  • Context and Types of context

References