Development
Mobile Developer
The ultimate database for mobile engineers, covering native Android (Kotlin/Java), native iOS (Swift), and cross-platform frameworks (React Native/Flutter).
What you will be asked about
How to prepare
- Go through the topic list above and mark every one you cannot explain for five minutes unprepared. Those are your gaps.
- Pair every concept with a story from your own work — interviewers probe depth, and depth comes from having actually done it.
- Do the DSA rounds anyway. Almost every role in this list still screens with coding.
- Prepare two projects you can whiteboard end to end, including what you would change now.
Also do
Mobile Developer interview questions500
Android Fundamentals20
An Activity is a single, focused thing a user can do, representing one screen with a user interface. Its lifecycle is a set of states managed by the OS: 1. onCreate(): Initial setup, inflating the UI. 2. onStart(): Activity becomes visible. 3. onResume(): User begins interacting; activity is at the top of the stack. 4. onPause(): Another activity comes into focus (e.g., a dialog); stop light animations here. 5. onStop(): Activity is no longer visible; release heavy resources. 6. onRestart(): Called when a stopped activity is being started again. 7. onDestroy(): Final cleanup before the activity is removed from memory.
An Activity is an independent component and an entry point for the user, typically representing a full screen. A Fragment is a modular portion of an activity’s UI; it must reside within an activity and cannot exist independently. Fragments are 'sub-activities' that help build multi-pane layouts (like tablet views) and are easier to reuse across different activities.
1. Activities: The entry point for user interaction (UI). 2. Services: Background components for long-running tasks (e.g., music playback). 3. Broadcast Receivers: Listeners for system-wide announcements (e.g., battery low, boot completed). 4. Content Providers: Manage and share app data with other apps (e.g., Contacts, Media Store).
An Intent is a 'messaging object' used to request an action from another component. - Explicit Intent: You specify the exact component by name (e.g., `Intent(context, targetActivity::class.java)`). Usually used for internal navigation. - Implicit Intent: You specify a general action (e.g., `ACTION_VIEW`, `ACTION_DIAL`) and let the OS find an app capable of handling it. For example, opening a URL in a browser.
Service is the base class for background tasks and runs on the Main Thread by default; you must manually create a worker thread for heavy tasks. IntentService is a subclass that automatically creates a worker thread, handles intents one-by-one in a queue, and stops itself once the work is done. Note: IntentService is deprecated in favor of WorkManager.
The Application lifecycle tracks the state of the entire process. It starts when any component is launched and ends when the OS kills the process. Key callbacks (via `Application.ActivityLifecycleCallbacks`) allow us to track when the app enters the background or foreground globally, which is useful for analytics or security locking.
A BroadcastReceiver allows your app to respond to system-wide events or app-to-app messages. You use it to detect changes like network connectivity loss, airplane mode toggles, or receiving a specific custom 'broadcast' from another part of your application to trigger a UI update.
A ContentProvider provides a secure, structured way to share data between different applications. It encapsulates data and provides a standard interface (via `ContentResolver`) for querying, inserting, and deleting records. It’s essential for apps that need to access system data like the Gallery or Contacts.
onCreate() is called only once when the activity is first created; this is where you do static setup (binding views, initializing data). onStart() is called every time the activity becomes visible to the user, even if they are returning from another screen or the background.
`onPause()` is called when the activity is still partially visible but losing focus (e.g., a semi-transparent dialog appears). You should stop light-weight animations, pause video playback, or commit small unsaved changes. Avoid heavy operations here because it blocks the next activity from starting.
By default, rotating the screen triggers a Configuration Change. The OS destroys the current activity instance and recreates it from scratch to apply new resources (e.g., a landscape layout). This requires the developer to handle state saving to prevent data loss.
1. ViewModel: The modern way to hold UI data; it survives configuration changes. 2. onSaveInstanceState(): Use the Bundle to save small amounts of transient state (like scroll position). 3. DataStore/Room: For persistent data that should survive the app process being killed.
Serializable is a standard Java interface that uses reflection, making it easy to implement but slow and memory-intensive. Parcelable is an Android-specific interface where you manually write the serialization logic; it is much faster and highly optimized for inter-process communication (IPC).
Parcelable is faster because it avoids the overhead of Java reflection. Developers explicitly define how each field is written to and read from a 'Parcel,' allowing the system to handle the data much more efficiently during intent passing.
A Bundle is a collection of key-value pairs (using Strings as keys) designed to pass data between components (via Intents) or to save state. It supports primitive types and objects that are `Parcelable` or `Serializable`.
Activity Context is tied to the Activity lifecycle; use it for UI operations (showing dialogs, inflating layouts). Application Context is tied to the app process; use it for singletons or long-running tasks that shouldn't hold a reference to a specific activity (to avoid memory leaks).
Use `getContext()` (Activity context) for anything UI-related. Use `getApplicationContext()` for initializing global libraries, database instances, or objects that must exist beyond the lifecycle of a single screen.
A memory leak occurs when an object is no longer needed but is still held by a reference, preventing garbage collection. Prevention: 1. Avoid static references to Activities/Context. 2. Unregister listeners/receivers in `onStop` or `onDestroy`. 3. Use `WeakReference` for inner classes. 4. Use tools like LeakCanary.
ANR occurs when the main thread (UI thread) is blocked for too long (usually 5 seconds). To avoid it, always perform disk I/O, networking, and complex calculations on background threads using Coroutines, RxJava, or WorkManager.
The `AndroidManifest.xml` is the project's 'master map.' It contains: - App package name. - Permissions required (Internet, Camera). - Declarations of all components (Activities, Services, etc.). - Hardware requirements (OpenGL version). - App theme and launcher icon.
Android UI & Layouts20
Android provides several layout types to organize UI: 1. LinearLayout: Arranges children in a single row or column. 2. RelativeLayout: Positions views relative to each other or the parent. 3. ConstraintLayout: A flexible system that uses 'constraints' to create flat hierarchies (highly recommended). 4. FrameLayout: Designed to block out an area on the screen to display a single item, often used as a container for Fragments. 5. GridLayout: Places views in a rectangular grid.
LinearLayout is straightforward and aligns items linearly; however, complex UIs often require 'nesting' multiple LinearLayouts, which degrades performance. RelativeLayout allows you to position a view relative to another (e.g., 'to the left of Button A'), which helps flatten the view hierarchy but can become computationally expensive for very complex layouts due to multiple measurement passes.
ConstraintLayout allows you to build complex, responsive UIs with a flat view hierarchy. It is recommended because it avoids the performance cost of nested layouts and is highly optimized for the Android Studio Layout Editor. It provides powerful features like Guidelines, Barriers, and Chains that are not available in older layouts.
match_parent tells the view to expand to be as large as its parent container (minus padding). wrap_content tells the view to be only as large as its internal content (e.g., a TextView just wide enough to fit its text).
RecyclerView is a more advanced and flexible version of ListView. Key differences: 1. Mandatory ViewHolder: It forces the use of the ViewHolder pattern for performance. 2. LayoutManager: It supports vertical, horizontal, and staggered grids. 3. ItemAnimator: It has built-in support for item animations. 4. Adapter: It allows for more granular updates via `DiffUtil`.
RecyclerView implements a 'Recycler' pattern. As an item scrolls off the screen, its View object is not destroyed; instead, it is placed in a pool and 're-bound' with new data for an item entering the screen. This drastically reduces expensive `findViewById()` calls and object allocations, leading to smooth 60fps scrolling.
The ViewHolder pattern holds references to the IDs of the views inside a list item. Without it, the system calls `findViewById()` every time a row is drawn, which is slow. By caching these references, the RecyclerView can instantly update the UI elements with new data without traversing the entire View tree again.
1. Attach an `OnScrollListener` to the RecyclerView. 2. In `onScrolled()`, check the `LinearLayoutManager` to see if the `lastVisibleItemPosition` plus a threshold is greater than the `totalItemCount`. 3. Trigger a background API call to fetch more data and append it to the adapter’s list using `notifyItemRangeInserted()`.
DiffUtil is a utility class that calculates the difference between two lists (Old vs New) and updates only the items that changed. It uses the Myers' difference algorithm. It is much more efficient than calling `notifyDataSetChanged()`, which refreshes the entire list and breaks animations.
A View is the basic building block of UI (e.g., Button, TextView, ImageView). A ViewGroup is an invisible container that holds multiple Views and other ViewGroups (e.g., ConstraintLayout) and defines their layout properties. All Layouts are ViewGroups.
A Custom View is a specialized UI component built by extending the `View` class (or existing widgets). You create one by overriding `onDraw()` to render graphics manually using a `Canvas` and `Paint`, and `onMeasure()` to determine the view's size based on parent constraints.
1. In XML: Using the `android:onClick` attribute (rarely used now). 2. In Code: Setting an `onClickListener` on the View object (e.g., `button.setOnClickListener { ... }`). 3. Interface: Implementing `View.OnClickListener` in the Activity/Fragment and passing `this` to the view.
The XML attribute is simpler for beginners but requires a public method in the Activity, making it hard to use with Fragments or complex logic. setOnClickListener is handled in the source code, allowing for lambda expressions, better separation of concerns, and dynamic behavior changes.
Fragments are modular portions of a UI that live inside an Activity. They are useful because they promote reusability (using the same fragment in different screens) and enable adaptive layouts (e.g., showing a list and detail pane on a tablet, but only one at a time on a phone).
Fragments have a lifecycle tied to their host Activity but include extra steps: - onAttach(): Fragment associated with activity. - onCreateView(): Setup the layout UI. - onViewCreated(): UI is ready; start logic. - onDestroyView(): Clean up UI references. - onDetach(): Fragment disassociated from activity.
Modern Android development uses a Shared ViewModel. Both fragments observe the same ViewModel instance scoped to their host Activity. For simple, one-way data passing, the Fragment Result API is used. Older methods involved creating custom interfaces implemented by the Activity.
add() adds a new fragment on top of the existing one; the previous fragment remains in the 'Active' state. replace() removes the existing fragment (moving it to `onPause` -> `onStop` -> `onDestroyView`) and adds the new one. Use `replace()` to save memory unless you need to overlay views.
A DialogFragment is a Fragment subclass used to display a floating dialog. You should use it instead of a standard `AlertDialog` because it correctly handles lifecycle events (like rotation) so the dialog doesn't disappear when the screen orientation changes.
A Dialog is a basic window managed manually by the activity; it's prone to memory leaks and crashes on configuration changes. A DialogFragment is a full Fragment that manages a dialog, making it much more robust and integrated into the standard Android lifecycle.
Data is passed using an Intent via `putExtra()`. For simple data, you pass primitives or Strings. For complex objects, the object must implement `Parcelable` (recommended for performance) or `Serializable`. The target Activity retrieves it using `intent.getParcelableExtra()`.
Architecture & Design Patterns20
MVVM stands for Model-View-ViewModel. 1) Model: Data layer (Room, Retrofit). 2) View: UI layer (Activity/Fragment) that observes data. 3) ViewModel: Acts as a bridge, holding UI-related data that survives configuration changes. It uses LiveData or Flow to notify the View of changes, ensuring a clear separation of concerns.
In Model-View-Controller, the Activity acts as the Controller. The Model handles data, the View is the XML layout, and the Controller handles user input and updates the Model. However, in Android, Activities often become 'God Objects' because they handle both UI and logic, making MVC difficult to test and maintain.
Model-View-Presenter introduces a Presenter that handles all logic. Unlike MVC, the Presenter has a one-to-one relationship with the View via an interface. The View is 'dumb' and only does what the Presenter says. This makes it more testable than MVC, but it requires a lot of boilerplate code.
MVVM is preferred because it is lifecycle-aware (using ViewModel) and promotes reactive programming. In MVP, the Presenter holds a reference to the View, which can lead to memory leaks if not handled. In MVVM, the ViewModel doesn't know about the View; it just exposes data, making it more robust and easier to use with Jetpack components.
ViewModel is a class designed to store and manage UI-related data in a lifecycle-conscious way. It is important because it survives configuration changes (like screen rotation). Without ViewModel, you'd have to manually save and restore data, which is error-prone and inefficient for large datasets.
LiveData is an observable data holder class. Unlike a regular observable, LiveData is lifecycle-aware, meaning it only updates app component observers (Activities/Fragments) that are in an active state (`STARTED` or `RESUMED`). This prevents crashes due to stopped activities and memory leaks.
LiveData is lifecycle-aware and integrated with the Jetpack ecosystem. ObservableField is part of the Data Binding library; it is not lifecycle-aware. Use LiveData for data that flows from ViewModel to View, and ObservableField for simple properties inside a Data Bound layout.
MutableLiveData allows you to change the stored value using `setValue()` (main thread) or `postValue()` (background thread). LiveData is read-only. We typically keep MutableLiveData `private` inside the ViewModel and expose it as a public `LiveData` to prevent the View from modifying data directly.
DataBinding is a library that allows you to bind UI components in your layouts directly to data sources in your app using a declarative format in XML. It reduces boilerplate code in Activities/Fragments by removing the need for `findViewById()` and manual UI updates.
Two-way binding uses the `@={variable}` syntax. It means that not only does the UI update when the data changes, but the data in the ViewModel also updates automatically when the user interacts with the UI (e.g., typing in an `EditText`).
ViewBinding only provides a way to interact with views safely (replaces `findViewById`). It is faster and lighter. DataBinding does everything ViewBinding does plus allows for binding data directly in XML and two-way binding. Use ViewBinding unless you specifically need DataBinding features.
Jetpack Compose is Android’s modern declarative UI toolkit. Instead of manipulating XML views (Imperative), you describe what the UI should look like for a given state using Kotlin functions (Declarative). It results in less code, faster development, and easier state management.
The Repository pattern acts as a mediator between different data sources (Persistent DB, Web Service, Cache) and the rest of the app. It provides a clean API for the ViewModel to get data without the ViewModel knowing *where* the data is coming from.
Dependency Injection (DI) is a technique where an object receives its dependencies from an external source rather than creating them itself. It is useful for decoupling code, making it easier to swap implementations (e.g., switching from a real API to a Mock API for testing) and improving maintainability.
Dagger is a fully static, compile-time dependency injection framework for Java and Kotlin. It generates code to handle the 'wiring' of dependencies. While powerful and efficient, it is known for having a steep learning curve due to its complex annotations like `@Component` and `@Module`.
Hilt is a library built on top of Dagger to simplify DI for Android. It removes much of the boilerplate by providing predefined components for Android classes (Activity, Fragment, ViewModel). Hilt is now the recommended way to implement DI in Android apps.
Koin is a lightweight, DSL-based DI framework for Kotlin. Unlike Dagger/Hilt, it uses Service Locator pattern internally and resolves dependencies at runtime. It is much easier to set up and learn but lacks the compile-time safety and performance optimizations of Dagger.
It's a collection of libraries that help you design robust, testable, and maintainable apps. Key components include Room (persistence), ViewModel (UI state), LiveData (lifecycle-aware observables), WorkManager (background tasks), and Navigation (in-app flow).
Room is an abstraction layer over SQLite. It provides compile-time verification of SQL queries, reduces boilerplate code for database setup, and integrates seamlessly with LiveData and Flow for reactive UI updates.
1) Room checks SQL queries at compile-time (SQLite only at runtime). 2) Room maps database rows directly to POJO/Data classes (SQLite requires manual Cursor mapping). 3) Room handles database migrations much more easily than raw SQLite.
Kotlin20
Kotlin offers: 1) Null Safety: Prevents NullPointerExceptions via type system. 2) Conciseness: Reduces boilerplate (e.g., data classes, properties). 3) Interoperability: 100% compatible with existing Java code. 4) Coroutines: Native support for efficient asynchronous programming. 5) Functional Programming: Support for lambdas, higher-order functions, and lazy loading.
Kotlin distinguishes between nullable (`String?`) and non-nullable (`String`) types at compile-time. If you try to call a method on a nullable type without a safe call (`?.`), a not-null assertion (`!!`), or a null check, the code won't compile, drastically reducing 'The Billion Dollar Mistake' (NullPointerExceptions).
val (Value) is immutable; once assigned, its reference cannot be changed (similar to `final` in Java). var (Variable) is mutable; its value can be reassigned multiple times during execution.
A `data class` is a concise way to create classes that only hold data. The compiler automatically generates `equals()`, `hashCode()`, `toString()`, and `copy()` methods based on the properties defined in the primary constructor, saving a lot of manual coding.
== checks for structural equality (calls `equals()` internally to check if the content is the same). === checks for referential equality (checks if two references point to the exact same object in memory).
Extension functions allow you to 'add' new methods to existing classes (even those you don't own, like `String` or `View`) without inheriting from them. Example: `fun String.removeSpaces() = this.replace(" ", "")`.
A lambda is an anonymous function that can be treated as a value. You can pass it as an argument to functions, return it, or store it. Syntax: `{ input -> body }`. They are heavily used in Android for click listeners and collection processing.
A higher-order function is a function that takes another function as a parameter or returns a function. This is a core concept of functional programming in Kotlin, enabling patterns like `filter`, `map`, and custom DSLs.
These are scope functions. 1) apply: Returns the object itself; used for initialization. 2) let: Returns the result of the lambda; often used for null checks. 3) also: Returns the object; used for side effects like logging. 4) run: Returns the result of lambda; used for complex initialization. 5) with: Similar to run but used when you already have the object reference.
Coroutines are 'lightweight threads' for asynchronous programming. They allow you to write asynchronous code (like network calls) in a sequential, synchronous-looking style without blocking the main UI thread. They are much more memory-efficient than standard Java threads.
Threads are managed by the OS and are expensive to create/switch. Coroutines are managed by the Kotlin runtime and are extremely cheap; you can run thousands of coroutines on a single thread. Coroutines use suspension instead of blocking, which keeps the underlying thread free for other tasks.
A `suspend` function is a function that can be paused and resumed later. It can only be called from another suspend function or a coroutine scope. While suspended, the thread is not blocked—it is free to do other work until the suspend function returns a result.
launch is 'fire and forget'; it returns a `Job` and is used for tasks that don't return a result. async is used when you expect a result; it returns a `Deferred<T>`, and you call `.await()` to get the final value.
Scopes manage the lifecycle of coroutines. viewModelScope: Tied to ViewModel (cancelled when VM is cleared). lifecycleScope: Tied to Activity/Fragment lifecycle. GlobalScope: Lives as long as the application (rarely used as it causes memory leaks if not managed).
Flow is a cold stream of data from the Kotlin Coroutines library. Unlike LiveData, Flow is not lifecycle-aware by default (though it can be used with `repeatOnLifecycle`), it handles backpressure, and it provides powerful operators like `map`, `filter`, and `zip` for complex data streams.
Flow is 'Cold' (it doesn't produce data until someone collects it; each collector gets its own stream). Channel is 'Hot' (it produces data even if no one is listening; data is shared/consumed by whoever is listening). Channels are for communication between coroutines; Flows are for streaming data.
A `sealed class` represents a restricted class hierarchy. All subclasses must be defined in the same file. It is useful for representing state (e.g., `Success`, `Error`, `Loading`), allowing the `when` expression to be exhaustive without needing an `else` branch.
The `object` keyword is used to declare a Singleton in a single step. It also handles thread-safe initialization of that singleton instance by the Kotlin runtime.
Since Kotlin doesn't have `static` members, a `companion object` is used inside a class to define members that can be accessed via the class name. It acts like a container for static methods and properties related to that class.
An `inline` function tells the compiler to copy the function's bytecode (and the bytecode of any passed lambdas) directly into the call site. This reduces the overhead of creating anonymous class objects for lambdas, improving performance in high-frequency calls.
Networking20
Modern networking is done using Retrofit or Ktor (for Kotlin Multiplatform). You define an interface for your API, use OkHttp as the networking client, and Gson or Kotlin Serialization to parse JSON. You must execute these calls on a background thread using Coroutines to avoid ANR.
Retrofit is a type-safe HTTP client for Android and Java. It is popular because it turns your HTTP API into a Java/Kotlin interface, handles JSON parsing automatically, supports Coroutines/RxJava, and has a great plug-in system (Interceptors).
In Retrofit, responses are usually handled via `Response<T>` objects or direct return types when using Coroutines. You check `response.isSuccessful()` to verify the 2xx status code. For complex apps, we wrap results in a `Result` or `Resource` sealed class to represent `Success`, `Error`, and `Loading` states in the UI.
OkHttp is the underlying HTTP client that Retrofit uses to execute network requests. It handles connection pooling, GZIP compression, and response caching. It is also where we add Interceptors to modify headers, log network traffic, or handle authentication tokens globally.
The best way is using an OkHttp Interceptor. You create an interceptor that intercepts every outgoing request and adds the `Authorization: Bearer <token>` header. If the token is expired, the interceptor can also handle the 401 error, refresh the token, and retry the original request automatically.
Synchronous calls block the current thread until the response arrives (never use this on the Main thread as it causes ANR). Asynchronous calls (using `enqueue` or Coroutines) execute the request in the background and notify the app via a callback or a resumed function when the result is ready.
I use a `try-catch` block around the API call to catch exceptions like `IOException` (no internet) or `HttpException` (server errors). I then map these exceptions to user-friendly messages. In a clean architecture, this logic is usually placed in the Repository layer.
Gson is a library that converts Java/Kotlin objects to JSON and vice-versa. In Retrofit, you add a `GsonConverterFactory`. The library uses reflection to match JSON keys with the variable names (or `@SerializedName` annotations) in your data classes.
Gson is older and uses reflection. Moshi is modern, built specifically for Kotlin/Java, and is faster because it uses code generation (via `kapt` or `ksp`). Moshi also has better support for Kotlin's null safety and default parameters.
I use OkHttp Cache. I define a cache directory and size, then add a `Cache-Control` header to requests via an Interceptor. This allows the app to load data from the local storage if the network is unavailable or if the data hasn't expired yet.
WorkManager is a Jetpack library for deferrable, guaranteed background work. It handles system constraints like battery level or network availability and ensures that the task runs even if the app is closed or the device is restarted.
Use WorkManager for tasks that don't need to happen *instantly* but must finish (e.g., uploading a log or syncing a DB). Use a Foreground Service for tasks that need to run immediately and the user is aware of (e.g., music playback or navigation).
I use Retrofit's `@Multipart` and `@Part` annotations. The file is wrapped in a `RequestBody` (often using `MultipartBody.Part`) and sent as a POST request. For large files, it is safer to use WorkManager to ensure the upload finishes in the background.
For large files, the DownloadManager system service is preferred. It handles background downloads, auto-retries on network failure, and shows a system notification for progress. Alternatively, Retrofit can be used with the `@Streaming` annotation to process the byte stream without loading it all into memory.
HttpURLConnection is a low-level, built-in Java class; it requires a lot of manual code for headers, input/output streams, and parsing. Retrofit is a high-level abstraction built on top of OkHttp that automates all these tasks, making the code much cleaner and less error-prone.
SSL Pinning ensures the app only communicates with a server that has a specific certificate. I implement this in OkHttp by creating a `CertificatePinner` object with the server's public key hash and adding it to the `OkHttpClient`. This prevents Man-in-the-Middle (MITM) attacks.
JSON (JavaScript Object Notation) is a lightweight data format. It is parsed using libraries like Gson, Moshi, or Kotlin Serialization. You define a data class matching the JSON structure, and the library automatically maps the keys to the object properties.
Pagination is handled by sending `page` and `limit` parameters in the API query. On the Android side, we use the Paging 3 Library from Jetpack, which handles loading the next page automatically as the user scrolls through a RecyclerView.
REST gives you a fixed data structure for each endpoint. GraphQL allows the client to request exactly the fields it needs. This reduces 'over-fetching' (getting data you don't use) and 'under-fetching' (needing multiple calls for one screen). In Android, we use the Apollo library for GraphQL.
I use the OkHttp WebSocket listener. It allows for a persistent, two-way communication channel between the client and server, which is essential for real-time features like chat apps or live sports scores.
Data Storage20
1. SharedPreferences/DataStore: For small key-value pairs. 2. Room Database: For structured, relational data. 3. Internal Storage: Private files. 4. External Storage: Shared files (images, downloads). 5. Encrypted Storage: For sensitive info using the Security library.
SharedPreferences is a framework to store small amounts of primitive data as key-value pairs in an XML file. You should use it for simple app settings, user preferences (like dark mode toggle), or small flags. It is not suitable for large datasets or complex objects because it performs synchronous disk I/O on the main thread when using `.commit()`.
Jetpack DataStore is the modern replacement for SharedPreferences. 1) DataStore uses Kotlin Coroutines and Flow to handle data asynchronously (preventing UI blocking). 2) It safely handles runtime exceptions. 3) Proto DataStore allows storing typed objects using Protocol Buffers, whereas SharedPreferences only handles primitives.
SQLite is an open-source relational database embedded into Android. It stores data in a text file on the device. While powerful, using raw SQLite is discouraged because it requires significant boilerplate code (SQL queries, Cursors, and manual database management) which is prone to errors.
1) Define an Entity (a data class representing a table). 2) Create a DAO (interface for SQL queries). 3) Create a RoomDatabase abstract class to serve as the main access point. 4) Use the Room builder in your Repository to instantiate the database.
A DAO is an interface where you define the methods used for accessing the database. You use annotations like `@Query`, `@Insert`, `@Update`, and `@Delete`. Room generates the implementation code at compile time, ensuring your SQL is correct and mapping results directly to Kotlin objects.
These are convenience annotations. @Insert adds new rows (can handle conflicts via `onConflict`). @Update modifies existing rows by matching primary keys. @Delete removes specific rows. Room handles the heavy lifting of writing the specific SQL 'WHERE' clauses for you.
When you change the schema (e.g., adding a column), you must increment the version number and provide a `Migration` object that defines the SQL `ALTER TABLE` commands. If no migration is provided and the version changes, Room will crash the app unless `fallbackToDestructiveMigration()` is called (which clears the DB).
Internal Storage: Private to the app; files are deleted when the app is uninstalled. External Storage: Shared space (SD Card/Public folders); other apps can access these files (with permission), and they persist after uninstallation.
For modern Android (10+), you should use the MediaStore API or Storage Access Framework (SAF) to save files to public directories like 'Pictures' or 'Downloads'. For older versions, you'd use `File` APIs with `WRITE_EXTERNAL_STORAGE` permission.
Scoped Storage restricts apps from seeing files created by other apps unless explicitly granted. Apps get a private folder for their files but must use the `MediaStore` API for shared media (images/videos) and the System Picker for other documents. This improves user privacy and reduces file clutter.
Since Android 6.0, you must: 1) Declare permissions in the Manifest. 2) Call `ActivityCompat.requestPermissions()`. 3) Handle the result in `onRequestPermissionsResult()`. For Android 13+, you must request specific permissions like `READ_MEDIA_IMAGES` instead of the general `READ_EXTERNAL_STORAGE`.
Part of the Jetpack Security library, it wraps standard SharedPreferences and automatically encrypts both keys and values. This is essential for storing sensitive data like OAuth tokens or session IDs that shouldn't be visible if the device is rooted or the XML file is accessed.
Use the Android Keystore System to generate and store cryptographic keys that are hardware-backed. Use these keys with the `Cipher` class (AES/RSA) to encrypt data before saving it to a file or database. The Security library's `MasterKey` and `EncryptedFile` classes simplify this process.
A NoSQL cloud-hosted database that stores data as one large JSON tree. It provides real-time synchronization; when data changes in the cloud, all connected clients receive the update in milliseconds. It is 'hot'—optimized for low-latency updates like chat apps.
Firestore is Google's newer NoSQL database. Unlike Realtime DB's JSON tree, Firestore uses a Document-Collection model. It offers better scalability, more powerful queries (shallow queries), and a more robust multi-region architecture. It's generally preferred for complex, large-scale mobile apps.
The standard pattern is Single Source of Truth (SSOT): 1) Fetch data from the network. 2) Save it into the Room Database. 3) The UI observes the Room DB. This ensures the app always displays data even without internet, and the UI updates automatically once the network sync completes.
The Repository acts as a mediator between the ViewModel and data sources (Local Room DB vs Remote API). The ViewModel simply asks the Repository for data; the Repository decides whether to fetch from the network or provide a cached version from the database.
Use WorkManager to schedule a sync task. The task should: 1) Identify local changes (dirty flags). 2) Push them to the API. 3) Download fresh data from the server. 4) Update the local Room DB. WorkManager ensures this happens even if the app is closed or the device reboots.
A `ContentResolver` is the object used to communicate with a `ContentProvider`. It provides methods like `query()`, `insert()`, and `delete()`. You use it to access data from other apps, such as reading the user's contacts or querying the MediaStore for local photos.
Background Processing20
A Service is a component that performs long-running operations in the background without a UI. Types: 1) Foreground: User is aware (shows notification, e.g., music). 2) Background: Invisible to user (limited in newer Android versions). 3) Bound: Allows other components to interact with it via a client-server interface.
A Started Service is launched via `startService()` and runs indefinitely until it stops itself or is killed, even if the component that started it is destroyed. A Bound Service offers a client-server interface that allows components (like Activities) to interact with the service, send requests, and get results. It runs only as long as another app component is bound to it.
A Foreground Service performs work that is noticeable to the user. It must display a non-dismissible status bar notification. You use it for tasks like music playback, active navigation, or tracking a fitness workout, ensuring the system doesn't kill the process when memory is low.
Inside `onStartCommand()`, you create a `Notification` object using `NotificationCompat.Builder` and then call `startForeground(ID, notification)`. On Android 8.0+, you must also associate the notification with a `NotificationChannel`.
JobScheduler is an API for scheduling various types of jobs that the system runs in its own process. It is battery-efficient as it batches jobs from different apps and waits for optimal conditions, such as the device being on a charger or connected to Wi-Fi.
AlarmManager provides access to the system alarm services. It allows you to schedule your application to run at a specific time in the future. Use it for time-sensitive tasks like a daily alarm clock or a scheduled reminder. It is not intended for networking or heavy background syncs.
JobScheduler is only available on API 21+ and requires manual handling of backward compatibility. WorkManager is a wrapper that uses JobScheduler on newer devices and AlarmManager/BroadcastReceivers on older ones. WorkManager also provides persistent task execution, even if the app reboots, which JobScheduler alone does not handle as easily.
The recommended way is using `PeriodicWorkRequest` in WorkManager. You define the interval (minimum 15 minutes) and the constraints (e.g., `setRequiredNetworkType(NetworkType.CONNECTED)`). WorkManager handles the rescheduling and execution logic automatically.
Doze mode is a power-saving state introduced in Android 6.0 that restricts apps' access to network and CPU-intensive services when the device is stationary and the screen is off. It affects background tasks by deferring jobs and alarms to specific 'maintenance windows' to preserve battery life.
App Standby is a system feature that identifies apps that the user hasn't interacted with for a specific period. The system puts these apps into a restricted state, limiting their access to the network and deferring their jobs to save power.
For background location, you must request `ACCESS_BACKGROUND_LOCATION` permission. Due to battery restrictions, updates are infrequent. For continuous tracking, you should use a Foreground Service with the `location` service type to ensure the system keeps the update frequency high.
It is the Google Play Services API for location. It 'fuses' data from GPS, Wi-Fi, and cellular networks to provide the most accurate location with the lowest power consumption. It is preferred over the legacy `LocationManager` because it handles the complex switching between providers automatically.
1) Register the app in Firebase Console. 2) Add the `google-services` JSON and SDK. 3) Extend `FirebaseMessagingService`. 4) Override `onMessageReceived()` to handle incoming data and `onNewToken()` to send the device token to your backend.
When an FCM message is received in `onMessageReceived()`, you use `NotificationManager` to display the content. You must handle different priorities, set a notification channel for Android 8.0+, and define a `PendingIntent` to open a specific Activity when the user taps the notification.
Local Notifications are triggered by the app itself on the device (e.g., a timer or a geofence trigger). Remote (Push) Notifications are sent from a server via a service like FCM to the device, usually to alert the user about external events like a new message.
Starting with Android 8.0, all notifications must be assigned to a channel. You create a `NotificationChannel` object with an ID, name, and importance level, and then register it with the system's `NotificationManager`. This allows users to block or customize specific categories of notifications.
A Looper is a class used to run a message loop for a thread (by default, the Main thread has one). A Handler is the interface used to send messages or 'runnables' to that looper's queue. Together, they allow you to communicate between background threads and the UI thread.
AsyncTask was a helper class to perform short background tasks and update the UI. It was deprecated because it easily caused memory leaks (if the activity was destroyed before the task finished), lacked built-in support for configuration changes, and was difficult to manage for complex parallel tasks.
1) Kotlin Coroutines (Recommended): Modern, lightweight, and concise. 2) WorkManager: For deferrable tasks. 3) RxJava: For complex reactive streams. 4) Executors: Standard Java thread pooling for basic background work.
I use Kotlin Coroutines with `Dispatchers.IO` for network/disk tasks or `Dispatchers.Default` for CPU-intensive work. For tasks that must persist beyond the current screen, I use WorkManager. This ensures the Main thread (Dispatchers.Main) remains free to handle 60fps UI rendering.
iOS Fundamentals10
The iOS app lifecycle is managed by the system and involves states like: Not Running, Inactive (transitioning), Active (foreground), Background, and Suspended. These transitions are handled in `AppDelegate` or `SceneDelegate` via methods like `sceneDidBecomeActive` or `sceneDidEnterBackground`.
The UIViewController lifecycle manages how a view is loaded, displayed, and removed: 1) loadView(): Creates the view (rarely overridden). 2) viewDidLoad(): View is loaded in memory; perform one-time setup here. 3) viewWillAppear(): Called right before the view appears. 4) viewDidAppear(): View is on screen; start animations or API calls. 5) viewWillDisappear(): View is about to hide. 6. viewDidDisappear(): View is off-screen.
viewDidLoad is called once when the view is first loaded into memory; it's the place for static initialization. viewWillAppear is called every time the view is about to become visible (e.g., when returning from another screen). Use `viewWillAppear` to refresh data or update UI elements that might have changed while the view was hidden.
AppDelegate handles app-level events like finishing launching, entering background, or push notification registration. Since iOS 13, SceneDelegate handles the window and UI lifecycle, allowing for multiple instances of the same app (multi-window support). The AppDelegate remains the main entry point, but the UI management is shifted to SceneDelegate.
The UIViewController is the fundamental building block of an iOS app's UI. It acts as the 'Controller' in the MVC pattern, managing a single root view, handling user interactions, and coordinating the data flow between the Model and the View.
Frame is the view's location and size relative to its parent's coordinate system. Bounds is the view's location and size relative to its own coordinate system (usually starting at 0,0). Rotating a view changes its frame, but its bounds remain the same.
Auto Layout is a system that dynamically calculates the size and position of all views in a hierarchy based on constraints (rules) defined by the developer. It ensures the UI adapts correctly to different screen sizes, orientations, and localizations without hardcoding coordinates.
These are the individual rules that define an Auto Layout relationship. A constraint is mathematically expressed as `View1.Attribute = Multiplier * View2.Attribute + Constant`. For example, setting a button's width to be 0.5 of its parent's width.
A Storyboard represents the entire visual flow of multiple screens and their transitions (Segues). A XIB (XML Interface Builder) represents a single view or a small portion of a screen (like a custom TableViewCell). Storyboards are better for app flow; XIBs are better for reusable, modular components.
SwiftUI is a declarative framework where you describe the UI state and the system handles the rendering. UIKit is imperative, where you manually manage view hierarchies and state updates. SwiftUI requires less code, supports real-time previews, and works across all Apple platforms, but UIKit is still used for complex custom behaviors and legacy apps.
Swift Programming20
An Optional is a type that can hold either a value or `nil` (no value). It is important because it makes code safer by forcing the developer to explicitly handle cases where a value might be missing, preventing runtime crashes like 'null pointer exceptions'.
Optional Binding (`if let` or `guard let`) safely unwraps the value into a temporary variable to be used in a code block. Optional Chaining (`?.`) allows you to access a property or method of an optional; if the optional is nil, the entire chain returns nil gracefully without crashing.
if let provides the unwrapped value only inside the `if` block. guard let 'guards' the rest of the function: if the value is nil, it exits early (usually `return`), but if it has a value, that value is available for the entire remaining scope of the function.
The nil coalescing operator `a ?? b` unwraps an optional `a` if it has a value, or returns a default value `b` if `a` is nil. It is a shorthand for a ternary operator or an `if-else` check.
Closures are self-contained blocks of functionality that can be passed around and used in your code. They are similar to lambdas in other languages. They can capture and store references to variables and constants from the context in which they are defined.
An `@escaping` closure is a closure that is passed as an argument to a function but is called after the function returns (e.g., an asynchronous network callback). By default, closures are non-escaping to optimize memory management.
Structs are Value Types (copied when passed) and do not support inheritance. Classes are Reference Types (the same instance is shared) and support inheritance. Classes also have Deinitializers and use ARC (Automatic Reference Counting).
Use a Struct by default for data models, simple values, and when you want to avoid shared state/threading issues. Use a Class when you need inheritance, when you need to control the lifecycle of a specific instance (e.g., a Database manager), or when using UIKit which is class-based.
Value types (struct, enum, tuple) store data directly; assigning them creates a unique copy. Reference types (class, closure) store a pointer to the memory address; assigning them creates a new reference to the exact same data.
ARC is Swift’s memory management system. It automatically tracks and manages the memory usage of class instances. It keeps an instance in memory as long as at least one strong reference to it exists, and deallocates it when the reference count drops to zero.
A strong reference cycle occurs when two class instances hold strong references to each other, preventing ARC from deallocating either one (memory leak). To break it, you must define one of the references as `weak` or `unowned`. Common examples include the relationship between a ViewController and its Closure or Delegate.
Both prevent strong reference cycles. weak is used when the referenced instance has a shorter lifetime and can become `nil` (must be an optional). unowned is used when the referenced instance has the same or longer lifetime and is expected to never be `nil` during its use. Using `unowned` on a deallocated object results in a crash, while `weak` just returns `nil`.
1) `weak` is always an optional (`var`), while `unowned` is typically a non-optional (`let` or `var`). 2) When the referenced object is deallocated, a `weak` pointer is automatically set to `nil`. 3) `unowned` is slightly faster as it doesn't track nullability, but it's dangerous if the object is destroyed unexpectedly.
A Protocol defines a blueprint of methods, properties, and other requirements that suit a particular task. Classes, structs, and enums can 'adopt' or 'conform' to a protocol by providing actual implementations of those requirements. They are similar to interfaces in Java/C#.
POP is a paradigm that favors using protocols and protocol extensions over traditional class inheritance. It allows for better code reuse, composition, and avoids the complexities of deep inheritance trees. Protocols can be applied to value types (structs/enums), which cannot use inheritance.
Swift does not have abstract classes. A Protocol defines only the interface (though extensions can provide default behavior). Unlike abstract classes in other languages, one type can conform to multiple protocols. Protocols also work with both value types and reference types.
Extensions add new functionality to an existing class, structure, enumeration, or protocol type. They can add computed properties, new methods, initializers, and make an existing type conform to a protocol without having access to the original source code.
Inheritance creates a new child class that derives behavior from a parent and can override it. Extension adds new behavior to the *original* type itself. Extensions cannot override existing functionality or add stored properties; they are for additive behavior.
Generics allow you to write flexible, reusable functions and types that can work with any type, subject to requirements you define. For example, `Array<T>` is a generic collection that works whether the type is an `Int` or a `String`. This prevents code duplication and ensures type safety.
Type casting checks the type of an instance or treats it as a different superclass/subclass. as? is a conditional cast (returns an optional nil if it fails). as! is a forced cast (crashes if it fails). as is used for upcasting to a supertype.
iOS UI Development20
UITableView is a view that displays a single column of vertically scrolling rows. You use it by setting a `dataSource` (to provide data/cells) and a `delegate` (to handle selection/layout). It uses cell recycling to remain efficient.
UITableView is restricted to a single-column list. UICollectionView is much more flexible, allowing for multiple columns, grids, horizontal scrolling, and custom layouts (via `UICollectionViewLayout`). Use TableView for simple lists and CollectionView for everything else.
A reuse identifier is a string used to 'tag' a cell type in the recycling pool. When the table needs a new row, it calls `dequeueReusableCell(withIdentifier:)`. If a cell with that tag is available in the pool, it is reused instead of creating a new object from scratch.
1) Subclass `UITableViewCell`. 2) Create a XIB or design the cell in Storyboard. 3) Set the Reuse Identifier. 4) Register the cell with the TableView. 5) In `cellForRowAt`, dequeue the cell, cast it to your custom class, and populate the data.
It is a concrete layout object that organizes items into a grid with optional header and footer views. It handles line spacing, item spacing, and scrolling direction. For more complex 'Pinterest-style' layouts, you would create a custom subclass of `UICollectionViewLayout`.
You implement the `didSelectRowAt` method in the `UITableViewDelegate`. This method provides the `indexPath` of the row the user tapped. Typically, you use this to navigate to a detail screen or update the UI state.
A container view controller that manages a stack of child view controllers. It provides the navigation bar and back button functionality. It works on a Last-In, First-Out (LIFO) basis, where you 'push' new screens on and 'pop' them off.
Push adds a screen to the current `UINavigationController` stack (sliding in from the right). Present displays a screen modally (usually sliding up from the bottom), covering the current context and creating a parent-child relationship between the controllers.
A container view controller that displays a radio-style selection interface at the bottom of the screen. Each tab represents a distinct section of the app. It is used to organize the app into a small number of top-level categories.
1) Push/Present: Setting properties on the destination controller before navigation. 2) Delegation: Using a protocol to send data back to a previous screen. 3) Closures: Passing a callback block. 4) NotificationCenter: For broadcasting to multiple observers. 5) Combine/SwiftUI State: Using shared observable objects.
The Delegate pattern is a design pattern that allows one object to send messages to another object when a specific event happens. It involves three parts: a Protocol defining the messages, a Delegate (the sender), and a Conforming Object (the receiver). It is used extensively in UIKit for handling events in TableViews, TextFields, and more.
Delegation is a one-to-one communication channel with a strict contract (protocol); it's efficient and easy to follow. Notification (NotificationCenter) is a one-to-many communication; the sender doesn't know who is listening. Use delegation for tight coupling between two objects and notifications for global events (e.g., 'User logged out').
It is a centralized hub for broadcasting information within an app. Objects register with the center to receive specific notifications using a name. When an event occurs, an object posts a notification, and all registered observers are notified simultaneously.
KVO is a mechanism that allows objects to be notified of changes to specific properties of other objects. It is part of Objective-C's dynamic heritage and is often used in UIKit. However, in modern Swift, it is largely being replaced by Combine or Property Observers.
UIStackView is a container that automatically manages the layout of its subviews in a row or column. It handles the constraints for you. You should use it to simplify complex layouts, especially when views need to be dynamically hidden or shown, as the stack view will adjust the surrounding views automatically.
Priority is a value from 1 to 1000 that tells Auto Layout which constraints are most important. If two constraints conflict, the system will satisfy the one with the higher priority. This is essential for creating 'optional' constraints or handling UI that must change based on content size.
To enable self-sizing cells: 1) Set `rowHeight` to `UITableView.automaticDimension`. 2) Provide an `estimatedRowHeight`. 3) Ensure the constraints inside the cell's `contentView` are connected from the top to the bottom, allowing the system to calculate the height based on the content.
UIScrollView is a view that allows users to scroll through content that is larger than the screen. You must set its `contentSize` property (or use Auto Layout constraints) so the scroll view knows how far it can move in each direction.
UIView is the base class for all visual elements and handles drawing and touch events. UIControl is a subclass of UIView that adds the 'Target-Action' mechanism, making it easier to handle specific user interactions like taps, value changes, or editing events (e.g., UIButton, UISlider).
1) UIView.animate: The simplest way to animate properties like alpha, frame, or transform. 2) Core Animation (CALayer): For more complex, low-level graphics animations. 3) UIViewPropertyAnimator: Provides better control, allowing you to pause, reverse, or scrub through animations.
iOS Networking & Data15
The standard way is using URLSession. For more advanced features like certificate pinning or easier parameter encoding, many developers use Alamofire. In SwiftUI, we often combine URLSession with the Combine framework or modern async/await syntax.
URLSession is the native API provided by Apple for uploading and downloading content via HTTP. It supports background downloads when the app is suspended and provides a suite of delegate methods for handling authentication and redirects.
URLSession.shared is a singleton for simple requests that use the default system behavior. A custom URLSession allows you to configure specific behaviors like custom timeouts, caching policies, or using a specific delegate to handle complex authentication challenges.
Swift's `Codable` protocol (a typealias for `Encodable & Decodable`) allows for automatic conversion between data formats (like JSON) and Swift structs/classes. You use `JSONDecoder().decode(YourType.self, from: data)` to turn raw JSON into an object.
Alamofire is a popular Swift-based HTTP networking library. It provides a more elegant, chainable syntax over URLSession, automatic JSON response validation, and simplified handling of multipart file uploads and network reachability.
Commonly, we store a JWT or OAuth token. For security, these must be stored in the Keychain, not UserDefaults. We then include this token in the `Authorization` header of every `URLRequest`. Modern apps use ASWebAuthenticationSession for social logins (SSO).
The Keychain is a secure encrypted storage container for sensitive data like passwords, keys, and tokens. Unlike UserDefaults, data in the Keychain persists even if the app is deleted and can be shared across multiple apps from the same developer.
UserDefaults is a simple key-value store for lightweight data, such as user settings or flags (e.g., `isDarkModeEnabled`). It is stored as a `.plist` file and is not intended for large data or sensitive information.
Core Data is an object-graph and persistence framework. It is NOT just a database; it manages a collection of model objects, handles their relationships, provides undo/redo functionality, and can persist data to a SQLite file on disk.
SQLite is a relational database where you write SQL queries to interact with tables. Core Data is an object-oriented framework where you interact with objects in memory; it handles the SQL generation for you. Core Data is faster for object-level operations but uses more memory than raw SQLite.
NSFetchedResultsController is a controller that efficiently manages the results of a Core Data fetch request to display data in a UITableView or UICollectionView. It provides automatic updates when the underlying data changes, handles caching, and simplifies section management, making it the standard for data-heavy iOS lists.
Migrations occur when the data model changes. Lightweight migration is handled automatically by iOS if you only add/rename attributes. For complex changes, you must create a Mapping Model to manually define how old data transforms into the new structure, ensuring user data isn't lost during app updates.
Realm is a mobile-first, cross-platform NoSQL database that serves as a popular alternative to Core Data. It is known for being significantly faster, easier to set up (less boilerplate), and having a more modern, thread-safe API compared to the complex Core Data stack.
1) URLCache: For caching network responses at the HTTP level. 2) NSCache: An in-memory cache for objects (like images) that automatically clears when memory is low. 3) Disk Caching: Manually saving data to the Caches directory or using libraries like Kingfisher for images.
URLCache provides a composite in-memory and on-disk cache for URL requests. By using the standard `URLSessionConfiguration`, the system can automatically store and retrieve responses based on the server's 'Cache-Control' headers, significantly reducing data usage and loading times.
iOS Architecture10
MVC (Model-View-Controller) is Apple's legacy default pattern. The Model holds data, the View is the Storyboard/UI, and the Controller (UIViewController) mediates. A common pitfall is 'Massive View Controller,' where the controller becomes bloated with networking, logic, and UI code.
MVVM (Model-View-ViewModel) separates UI logic from the ViewController. The ViewModel transforms Model data into values the View can display and handles business logic. The View (ViewController) 'binds' to the ViewModel using Closures, Delegates, or the Combine framework.
VIPER is a clean architecture based on five components: View, Interactor, Presenter, Entity, and Router. It provides the highest level of modularity and testability by isolating logic into small, single-responsibility classes, though it requires significant boilerplate code.
The Coordinator pattern extracts navigation logic from ViewControllers into a dedicated 'Coordinator' class. This makes ViewControllers more reusable (they don't need to know which screen comes next) and simplifies complex navigation flows, like deep-linking or onboarding sequences.
A Singleton ensures a class has only one instance and provides a global point of access (e.g., `URLSession.shared`). Use it for shared resources like a Network Manager or Database. Avoid it for everything else, as it makes unit testing difficult due to 'hidden' global state.
The Factory pattern uses a dedicated method or class to create objects without specifying the exact class of the object that will be created. This is useful in iOS for creating different types of ViewControllers or Cells based on dynamic data at runtime.
The Observer pattern allows an object to notify multiple 'observers' about state changes. In iOS, this is primarily implemented via NotificationCenter, KVO, or modern Combine/SwiftUI `@Published` properties.
DI is passing an object's dependencies (like a Network Service) through the initializer or a property rather than letting the object create them itself. This is critical for Unit Testing, as it allows you to 'inject' mock services to test the object in isolation.
1) Initializer Injection (Preferred): Passing dependencies during `init`. 2) Property Injection: Assigning dependencies after creation. 3) Dependency Containers: Using libraries like Swinject to manage complex dependency graphs across the app.
The Repository pattern abstracts the data layer, providing a clean API for the ViewModel to fetch data. It decides whether to load data from a local Core Data store or a remote API, keeping the rest of the app unaware of the underlying data source details.
React Native25
React Native is a framework for building native apps using React and JavaScript. It uses a Bridge (or the newer JSI) to allow JavaScript code to communicate with native platform APIs, rendering actual native UI components instead of using a webview.
React is a library for building web UIs using HTML elements (`div`, `span`). React Native is a framework for building mobile UIs using native components (`View`, `Text`). RN does not use a browser DOM; it maps its components to Android and iOS native views.
The Bridge is a JSON-based asynchronous communication layer. JavaScript code sends messages to the Native side (Java/Swift) to perform UI updates or access device features, and the Native side sends responses back. In modern RN, this is being replaced by JSI (JavaScript Interface) for direct synchronous calls.
JSX is a syntax extension for JavaScript that looks like XML/HTML. It allows you to describe what the UI should look like. In React Native, JSX tags like `<View>` and `<Text>` are transpiled into function calls that the bridge uses to create native views.
Components are the building blocks of the UI. They are either Functional (using Hooks) or Class-based. Core components provided by RN include `View`, `Text`, `Image`, `ScrollView`, and `TextInput`.
View is the basic container component, similar to a `div` in web; it is used for layout (Flexbox) and styling. Text is the only component that can display strings. Unlike web, you cannot put raw text inside a `<View>` without a `<Text>` wrapper, or the app will crash.
FlatList is a high-performance component for rendering large, scrollable lists. It only renders the items currently visible on the screen and 'recycles' views, which minimizes memory usage and ensures smooth scrolling even with thousands of rows.
ScrollView renders all its children at once, which is fine for small amounts of content but slow for large lists. FlatList is lazy-loading; it only renders what’s on screen, making it the standard choice for feeds, contacts, or any data-heavy list.
Hooks are functions that let you 'hook into' React state and lifecycle features from functional components. They allow you to manage state without writing a class. The most common hooks are `useState`, `useEffect`, `useContext`, and `useRef`.
The `useState` hook allows you to add state to functional components. It returns a pair: the current state value and a function that lets you update it. Whenever the state is updated, the component automatically re-renders to reflect the changes.
The `useEffect` hook handles side effects like API calls or subscriptions. It runs after the render. If passed an empty dependency array `[]`, it runs only once (like `componentDidMount`). If passed variables in the array, it runs whenever those variables change.
The `useContext` hook allows you to subscribe to React Context. It provides a way to pass data through the component tree without having to pass props down manually at every level ('prop drilling'). It is great for global themes or user authentication data.
The `useRef` hook returns a mutable ref object that persists for the full lifetime of the component. It is commonly used to access a child component or a DOM-like element directly (e.g., to manually focus a `TextInput` or control a `ScrollView`).
Redux is a predictable state container for JavaScript apps. It is used in React Native to manage complex global state (like a shopping cart or user profile) in a single 'Store,' making data predictable and easier to debug across different screens.
Actions are plain objects that describe 'what happened' (e.g., `{type: 'ADD_ITEM'}`). Reducers are pure functions that take the current state and an action, then return a new state. They define 'how the state changes' in response to an action.
Context API is built into React and is ideal for low-frequency updates (themes, auth). Redux is an external library optimized for high-frequency updates and complex logic; it offers powerful debugging tools (Redux DevTools) and middleware support.
React Navigation is the standard library for routing and navigation in RN apps. It provides a way for apps to transition between screens and manage navigation history. It supports Stack, Tab, and Drawer navigators out of the box.
Using the `navigation` prop or the `useNavigation` hook. You call `navigation.navigate('ScreenName')`. For a stack-based flow, you use `navigation.push('ScreenName')` to add a new screen onto the stack.
Stack Navigator provides a way to transition between screens where each new screen is placed on top of a stack (slides in from right/bottom). Tab Navigator sets up a tab bar at the bottom (or top) of the screen that lets users switch between different sub-sections of the app.
You pass parameters as a second argument to the navigate function: `navigation.navigate('Details', { itemId: 86 })`. On the destination screen, you retrieve them using `route.params.itemId` from the `route` prop or `useRoute` hook.
AsyncStorage is an unencrypted, asynchronous, persistent, key-value storage system. It is used to store simple data like user tokens or settings. Note: It is being moved out of the core RN library into the Community package.
You can use the built-in `fetch` API or the Axios library. Usually, these calls are placed inside a `useEffect` hook or a Redux Thunk action to ensure they happen in the background without blocking the UI rendering.
Axios is a popular third-party HTTP client. It is preferred over `fetch` because it automatically transforms JSON data, has built-in support for request/response interceptors (ideal for auth tokens), and provides better error handling.
Using the `<Image>` component. It can load local images via `require('./image.png')` or remote images via a URI object `{ uri: 'https://...' }`. For remote images, you must explicitly set the width and height or they won't show up.
Image is for displaying a simple picture. ImageBackground is a container component that allows you to layer children (like Text or Views) on top of an image, similar to the CSS `background-image` property.
Flutter20
Flutter is a UI toolkit from Google that uses the Dart language. Unlike React Native, which bridges to native components, Flutter uses its own high-performance rendering engine (Impeller/Skia) to draw every pixel of the UI manually, resulting in extremely consistent performance and look-and-feel across all devices.
Dart is a client-optimized, object-oriented language developed by Google. It supports both Just-In-Time (JIT) compilation (for fast development/hot reload) and Ahead-Of-Time (AOT) compilation (for high-performance production code). It features sound null safety and is designed to be easy to learn for developers coming from Java, C#, or JavaScript.
In Flutter, 'Everything is a Widget.' A widget is an immutable description of a part of a user interface. It defines the configuration and state of UI elements, ranging from layout (Padding, Center) to structural elements (Buttons, Text) and even themes.
StatelessWidgets are immutable and their properties cannot change once built (ideal for static content). StatefulWidgets maintain a `State` object that persists over the widget's lifetime; when the state changes, the widget rebuilds to reflect the new data.
The `setState()` function notifies the Flutter framework that the internal state of a `StatefulWidget` has changed. This triggers the `build()` method to run again, allowing the UI to update with the new values. It should only be used for local, synchronous state changes.
BuildContext is a handle to the location of a widget in the Widget Tree. It is used to look up information higher in the tree (like Themes or MediaQueries) and to interact with the framework (like showing a SnackBar or navigating to a new route).
The Widget Tree is the hierarchy of all widgets used to build the UI. Flutter also manages an Element Tree (the logical link between widget and render object) and a RenderObject Tree (which handles the actual painting and layout), ensuring high-performance UI updates.
Hot Reload injects updated source code files into the running Dart Virtual Machine. Flutter then rebuilds the widget tree, allowing developers to see UI changes instantly (usually under 1 second) while preserving the app state.
Hot Reload updates the code while keeping the state (e.g., current screen and variable values). Hot Restart destroys the current state and restarts the app from the main entry point. Use Hot Restart when you modify static variables, global initializers, or the `main()` function.
Keys preserve the state of widgets when they move around the widget tree. You use them when you have a list of stateful widgets that change order (like a To-Do list) to ensure that the framework correctly maps the state to the corresponding widget after the move.
SizedBox is a simple widget to give a child specific dimensions or add whitespace; it's very lightweight. Container is much more complex, offering styling features like padding, margins, borders, backgrounds (decoration), and transformations.
ListView is the most commonly used scrolling widget. It displays its children one after another in the scroll direction. For large or infinite lists, use ListView.builder, which only builds the items that are visible on the screen to save memory.
ListView scrolls in a single dimension (linear). GridView scrolls in two dimensions, placing items in a table-like layout. Both support builder patterns for performance optimization with large datasets.
StreamBuilder is a widget that builds itself based on the latest snapshot of interaction with a Stream. It is used for handling continuous data updates, such as real-time chat messages, sensor data, or Firebase live updates.
FutureBuilder handles a one-time asynchronous operation. It waits for a `Future` to complete and provides different UI states based on whether the future is still `waiting`, has an `error`, or has finished with `data` (e.g., fetching a user profile once).
Provider is a wrapper around `InheritedWidget` that makes state management and dependency injection easier. It allows you to share data (like a user object or a theme) across different parts of the app without manually passing it through every widget constructor.
BLoC (Business Logic Component) uses Streams to separate the UI from the business logic. The UI sends 'Events' to the BLoC, and the BLoC processes them to emit 'States'. This makes the app highly testable and follows a strict reactive architecture.
GetX is an extra-light and powerful all-in-one solution for Flutter that handles high-performance state management, dependency injection, and route management without needing a BuildContext.
Riverpod is a complete rewrite of the Provider library that is compile-safe and doesn't depend on the Flutter SDK's Widget tree. It solves many limitations of Provider, such as being able to access providers outside the UI or from other providers easily.
Using the Navigator widget. You can use simple imperative navigation (`Navigator.push`) or named routes. For complex apps, the Navigator 2.0 (Router API) or libraries like `go_router` provide a declarative way to manage deep linking and complex page stacks.
Mobile Performance20
Memory leaks can be detected using: 1) Android Profiler (Heap Dump analysis). 2) LeakCanary (a library that automatically detects and notifies you of leaks in debug builds). 3) Checking for static references to Context or Activities that aren't cleared in `onDestroy`.
The Android Profiler in Android Studio provides real-time data for your app's CPU, Memory, Network, and Energy usage. You use it to inspect thread activity, record method traces to find 'janky' code, and capture heap dumps to find memory leaks or unneeded object allocations.
1) Use `setHasFixedSize(true)` if content won't change size. 2) Avoid heavy computations in `onBindViewHolder`. 3) Use `DiffUtil` for list updates. 4) Use image loading libraries like Glide/Picasso. 5) Set a `viewPool` for nested RecyclerViews to share view holders.
View recycling is the process of reusing a View object after it scrolls off the screen for a new item entering the screen. It is important because inflating new XML layouts is CPU-intensive; recycling keeps memory usage stable and ensures smooth scrolling by avoiding excessive garbage collection.
1) Enable ProGuard/R8 for code shrinking. 2) Use Android App Bundles (AAB) instead of APKs. 3) Optimize images (WebP format). 4) Use VectorDrawables instead of PNGs. 5) Use 'Lint' to remove unused resources.
ProGuard is a tool that shrinks, optimizes, and obfuscates code. R8 is the modern replacement developed by Google that does the same but faster and with better results. They remove unused classes and rename variables to short names (e.g., `a`, `b`) to make the app smaller and harder to reverse-engineer.
Lazy loading involves loading data only when it is needed. For lists, this means fetching more items as the user reaches the end (Pagination). For UI, it means using `ViewStub` (Android) or `Lazy` properties (Swift/Kotlin) to delay the initialization of heavy components until they are visible.
Image caching stores downloaded images in memory and on disk. Memory cache provides instant access, while disk cache prevents re-downloading over the network. It is usually implemented using a Two-tier cache system (LruCache for RAM, DiskLruCache for storage).
Glide is a powerful image loading and caching library. It handles complex tasks like downsampling (to match ImageView size), disk/memory caching, and automatic clearing of requests when an Activity or Fragment is destroyed to prevent memory leaks.
Picasso is simpler and smaller, but it downloads the full-size image and caches it. Glide is more complex but optimized for smooth scrolling; it downsamples images to the exact size of the ImageView before caching, saving significant memory. Glide also supports GIFs natively.
1) Use WorkManager to batch network requests. 2) Avoid keeping the screen awake (WakeLocks) unless necessary. 3) Use the Fused Location Provider for efficient location tracking. 4) Use 'Job Constraints' so tasks only run when the device is charging or on Wi-Fi.
Doze mode is a power-saving state that limits background CPU and network activity when a device is stationary with the screen off. It works by restricting apps to specific 'maintenance windows' where they can perform all their deferred background tasks at once to minimize wakeups.
iOS developers use Instruments (specifically the Time Profiler and Allocations tools). You monitor for high CPU spikes, 'Zombie' objects (memory that wasn't deallocated), and 'Hitches' (dropped frames) during UI interactions.
Instruments is a powerful performance-tuning tool for iOS. Key templates include: 1) Time Profiler: To find code that slows down the main thread. 2) Allocations/Leaks: To track memory usage. 3) Core Animation: To measure FPS and GPU rendering issues.
1) Enable App Thinning (Slicing). 2) Use HEIC/WebP for images. 3) Remove unused assets and localized strings. 4) Use 'On-Demand Resources' to download certain assets only when a user needs them. 5) Audit third-party frameworks to remove unnecessary code.
Bitcode is an intermediate representation of your compiled app. When you upload an app with bitcode enabled, Apple can re-compile and optimize the binary for different CPU architectures (like moving from Intel to Apple Silicon) without you having to submit a new version.
Most developers use libraries like SDWebImage or Kingfisher. These libraries extend `UIImageView` to handle asynchronous downloading, memory/disk caching, and placeholder management with a single line of code.
An Objective-C/Swift library providing an asynchronous image downloader with cache support. It features automatic cache expiration, background image decompression to avoid UI jank, and a category for `UIImageView` that handles the entire loading lifecycle.
A pure-Swift library for downloading and caching images from the web. It is highly optimized for performance, supports SwiftUI natively, and includes advanced features like image processing (filters/resizing) before caching.
1) Reuse cells with `dequeueReusableCell`. 2) Use opaque views where possible. 3) Cache cell heights so the table doesn't re-calculate them on every scroll. 4) Perform heavy data parsing on a background thread. 5) Avoid using `shadows` or `transparency` in complex cell hierarchies.
Mobile Testing20
Unit testing involves testing the smallest 'units' of code (like a single function or a ViewModel) in isolation. It ensures that the logic is correct without needing a real device or a network connection, usually by 'mocking' the dependencies.
JUnit is the standard testing framework for Java and Kotlin. In Android, it is used for local unit tests that run on the JVM. It uses annotations like `@Test` to define test cases, `@Before` for setup logic, and assertions like `assertEquals` to verify that the code behaves as expected.
Mockito is a mocking framework used to create 'fake' objects for unit testing. For example, instead of calling a real API, you create a mock `ApiService` that returns a predefined response. This allows you to test your ViewModel or Repository in complete isolation from the network or database.
Espresso is the official UI testing framework for Android. It allows you to write 'instrumented tests' that run on real devices or emulators. It provides a simple API for interacting with UI elements, such as `onView(withId(R.id.button)).perform(click())`, and automatically synchronizes test actions with the UI thread.
1) Use Espresso for standard View-based UIs. 2) Use Compose Test Library for Jetpack Compose UIs. 3) Define test rules to launch the activity. 4) Use Matchers to find views, ViewActions to interact, and ViewAssertions to check the final state.
XCTest is the integrated testing framework for Apple platforms. It supports unit tests, performance tests, and logic tests. It is built directly into Xcode, providing a 'Test Navigator' to run and visualize test results easily.
XCUITest is an extension of XCTest used for user interface testing. It uses Accessibility APIs to find and interact with UI elements (like buttons and labels). It simulates user actions like taps and swipes to ensure the end-to-end flow of the app works correctly.
Unit Tests verify individual components in isolation (e.g., a math function). Integration Tests verify how multiple components work together (e.g., checking if the Repository correctly saves data from the API to the local Database). Unit tests are faster; integration tests are more realistic.
TDD is a software development process where you write the test before writing the actual code. The cycle is: 1) Red: Write a failing test. 2) Green: Write the minimum code to pass the test. 3) Refactor: Clean up the code while ensuring the test still passes.
In Android, I use MockWebServer (from OkHttp) or Mockito. In iOS, I use URLProtocol stubs. The goal is to return a local JSON file or a hardcoded object instead of making a real network request, ensuring tests are fast and don't depend on server stability.
Code coverage is a metric showing the percentage of your source code executed during testing. In Android Studio, you can run tests with 'Coverage' to see which lines are hit. In iOS, Xcode has a built-in coverage tool in the 'Report Navigator'. Aim for high coverage in business logic (80%+).
Jest is a JavaScript testing framework optimized for React applications. It is the default for React Native and supports Snapshot Testing, which captures the rendered UI structure and alerts you if it changes unexpectedly in the future.
It is a lightweight library for testing React Native components. It encourages testing from the user's perspective (e.g., 'Does the screen show a button with text Login?') rather than implementation details (e.g., 'Is the button state set to true?').
Flutter provides three levels of testing: 1) Unit tests (Dart logic). 2) Widget tests (testing a single widget's UI and interaction). 3) Integration tests (running the full app on a device).
Widget testing is unique to Flutter; it allows you to render a widget in a test environment without a full device. You use the `WidgetTester` utility to pump a widget, find elements using `find.text()` or `find.byKey()`, and simulate interactions like taps.
It is a cloud-based app-testing infrastructure. You upload your APK or IPA, and Google runs it on a wide variety of real devices and configurations in their data center, providing logs, screenshots, and videos of any crashes found.
Appium is a cross-platform automation tool. It allows you to write UI tests in any language (Java, Python, JS) that work on both Android and iOS. It is the industry standard for QA Engineers who need to automate tests across both platforms with a single codebase.
Manual Testing involves a human following test cases; it's better for UI/UX 'feel' and exploratory testing. Automated Testing uses scripts; it's faster, repeatable, and essential for Regression Testing (ensuring new changes didn't break old features).
I use CI tools like GitHub Actions, Bitrise, or CircleCI. Every time code is pushed, the CI: 1) Builds the app. 2) Runs all Unit and UI tests. 3) Reports failures. 4) Deploys the build to testers (e.g., via Firebase App Distribution).
Fastlane is an open-source tool that automates tedious tasks. In testing, it can automatically run your test suites (`scan`), capture screenshots (`snapshot`), and distribute beta builds to TestFlight or Play Store (`boarding`), saving hours of manual work.
Mobile Security20
1) Never hardcode keys in the source code. 2) Use local.properties and fetch them via Gradle (Android) or use xcconfig (iOS). 3) Use Proguard/R8 to obfuscate the code. 4) Implement API Key Restriction (e.g., Google Cloud console) so the key only works for your app's package name/bundle ID.
SSL Pinning is a security technique that prevents Man-in-the-Middle (MITM) attacks by hardcoding the server's public key or certificate into the app. This ensures the app only communicates with a server that matches the 'pinned' certificate. In Android, it's implemented via OkHttp's `CertificatePinner`; in iOS, via `URLSessionDelegate` or libraries like TrustKit.
I use the BiometricPrompt API, which provides a standard system dialog for Fingerprint, Face, and Iris scans. It handles the underlying hardware complexity and returns a callback to the app. For sensitive operations, I combine this with the Android Keystore to unlock a cryptographic key only upon successful biometric verification.
The Keystore system lets you store cryptographic keys in a container to make them more difficult to extract from the device. Keys are often stored in TEE (Trusted Execution Environment) or a Secure Element, meaning the key material never enters the main Android OS memory, protecting it from root-level malware.
I use the Jetpack Security library. It provides `EncryptedSharedPreferences` for key-value pairs and `EncryptedFile` for larger files. It uses a Master Key stored in the Android Keystore to handle the AES encryption and decryption processes transparently and securely.
I use the LocalAuthentication (LAContext) framework. I first check `canEvaluatePolicy` to see if biometrics are available, then call `evaluatePolicy` to trigger the system prompt. If successful, the app is granted access. Like Android, this is best used to protect sensitive data stored in the Keychain.
Keychain Services is a secure database for small bits of data like passwords and tokens. Data in the Keychain is encrypted at rest and stays on the device even if the app is uninstalled. It is the only place developers should store persistent authentication tokens in iOS.
1) Obfuscation: Use R8/ProGuard (Android) or SwiftShield (iOS). 2) Root/Jailbreak Detection: Check for suspicious binaries (like Cydia or SuperSU). 3) Anti-Tampering: Check app signatures at runtime. 4) String Encryption: Encrypt sensitive strings so they aren't visible in plain text during static analysis.
Code obfuscation is the process of modifying the executable so that it is no longer useful to a hacker but remains functional. It renames classes, methods, and variables to meaningless characters (e.g., `class UserService` becomes `class a`), making it extremely difficult to read the logic if the app is decompiled.
I use a library like AppAuth. The app opens a 'System Browser' or `ASWebAuthenticationSession` to the provider (Google/GitHub). The user logs in there, and the provider redirects back to the app via a Custom URL Scheme with an authorization code, which the app exchanges for an Access Token.
JWT is a compact, URL-safe means of representing claims between two parties. It consists of a Header, Payload, and Signature. Once a user logs in, the server sends a JWT. The mobile app stores it securely (Keychain/Keystore) and sends it in the `Authorization` header for every API request.
In Android, use `EncryptedSharedPreferences`. In iOS, use Keychain. Never store tokens in plain-text SharedPreferences, UserDefaults, or a local database, as these are easily accessible on rooted or jailbroken devices.
Root detection is a series of checks to see if the user has administrative access to the OS. Apps check for the existence of `su` binaries, the presence of apps like Magisk, or by attempting to write to system-restricted directories. If root is detected, high-security apps (like Banking apps) may refuse to run.
Similar to root detection, jailbreak detection checks for files like `/Applications/Cydia.app`, the ability to write outside the app sandbox, or the presence of known jailbreak-only dynamic libraries. It is used to protect against unauthorized data access and app patching.
1) Get the server's public key hash. 2) In Android, add it to the `CertificatePinner` in OkHttpClient. 3) In iOS, implement the `didReceiveChallenge` delegate method in `URLSession` to compare the server's certificate against your hardcoded 'known good' hash.
HTTPS is HTTP over TLS (Transport Layer Security). It provides Encryption (prevents eavesdropping), Integrity (prevents tampering), and Authentication (proves the server is who it claims to be). Android and iOS now block non-HTTPS (Cleartext) traffic by default.
I ensure that sensitive data like passwords, PII (Personally Identifiable Information), or auth tokens are never logged. I use a logging utility like Timber in Android that only enables logging in debug builds and strips all logs from the production release.
SafetyNet (now being replaced by Play Integrity API) is a Google service that evaluates the 'health' and safety of the device. It checks if the device is rooted, tampered with, or running a custom ROM, providing a 'compatibility' signal to the backend to decide if the device should be trusted.
It's a standard list of the most critical security risks to mobile apps. Key risks include: Insecure Data Storage, Insecure Communication, Insecure Authentication, and Insufficient Binary Protection. Following this list is essential for building enterprise-grade secure apps.
1) Use HTTPS (TLS 1.2+). 2) Implement SSL Pinning. 3) Use Token-based authentication (JWT). 4) Encrypt the request payload for extremely sensitive data. 5) Use Short-lived Access Tokens and Refresh Tokens.
CI/CD & Deployment20
CI is the practice of automatically building and testing code every time a change is pushed. CD is the automatic release of that code to a testing environment or production. For mobile, this ensures that no 'broken' code reaches the App Store and speeds up the feedback loop for developers.
Fastlane is an open-source automation tool for iOS and Android. It handles tedious tasks like generating screenshots, dealing with code signing, and releasing builds to the App Store or Play Store. It uses 'Lanes' to group actions, allowing you to deploy a build with a single command like `fastlane deploy`.
I use CI tools like GitHub Actions, CircleCI, or Bitrise. I define a YAML workflow that: 1) Checks out the code. 2) Installs dependencies (npm/cocoapods). 3) Runs unit tests. 4) Executes a build command (e.g., `./gradlew assembleRelease` or `xcodebuild`). 5) Uploads the resulting artifact to a distribution service.
Jenkins is a self-hosted automation server. For mobile, it requires a dedicated macOS machine (for iOS builds) and a set of plugins to handle Gradle and Xcode. While powerful and free, it requires more maintenance compared to cloud-based solutions like Bitrise.
CircleCI is a cloud-based CI/CD platform that supports mobile environments. It provides managed macOS and Linux executors, allowing teams to build iOS and Android apps without managing their own server hardware. It uses a `config.yml` file to define the build pipeline.
GitHub Actions is an automation platform built directly into GitHub. It is popular for mobile because it is free for public repositories and has a massive marketplace of pre-built 'Actions' for things like setting up Java, installing provisioning profiles, or uploading to Firebase.
I use Build Flavors and Build Types in `build.gradle`. For example, I might have flavors for `dev`, `staging`, and `prod` (to point to different APIs) and types for `debug` and `release` (to handle logging and obfuscation).
Debug builds are signed with a generic key, include full symbols for debugging, and allow logging. Release builds are signed with a production key, have R8/ProGuard enabled for shrinking/obfuscation, and are optimized for performance and security for the end user.
App signing ensures that the APK/AAB hasn't been tampered with. Every Android app must be signed with a digital certificate. If a hacker tries to modify your app and re-distribute it, the signature will change, and the OS will prevent the installation over the original app.
1) Create a Keystore file (JKS). 2) In Android Studio, go to 'Build' -> 'Generate Signed Bundle/APK'. 3) Select the Keystore, enter passwords, and choose the build variant. 4) The resulting APK can then be manually uploaded to the Play Store or sent to testers.
AAB is the official publishing format for Google Play. Unlike an APK, which contains code for all device configurations, an AAB allows Google Play to generate and serve an optimized APK tailored for the specific device downloading it, resulting in smaller download sizes.
APK is the final executable that can be installed directly on a phone. AAB is a publishing format that cannot be installed on a device directly; it is used by the Play Store to build customized APKs for users. AABs are mandatory for new apps on Google Play.
1) Create a developer account on Google Play Console. 2) Create a new app entry. 3) Upload the AAB. 4) Complete the Store Listing (descriptions, screenshots). 5) Fill out Content Rating and Privacy Policy. 6) Start a rollout to the Production track.
It is the web-based command center for Android developers. It allows you to manage releases, view crash reports (Android Vitals), check store analytics, manage pricing/subscriptions, and respond to user reviews.
When releasing a new version, I can choose a 'Staged Rollout' and select a percentage (e.g., 5%, 10%). This releases the update to a small group of users first. I monitor for crashes; if everything is stable, I increase the percentage to 100%.
TestFlight is Apple's official beta testing platform. It allows you to invite up to 10,000 external testers to try your app before it's released on the App Store. Testers install the 'TestFlight' app on their device to download and provide feedback on your beta builds.
1) Build and Archive the app in Xcode. 2) Upload the archive to App Store Connect. 3) Create a new version in App Store Connect. 4) Provide metadata and screenshots. 5) Submit for 'App Review.' 6) Once approved, the app goes live.
It is Apple's web portal (companion to the Developer site) used to manage apps, view sales data, set up In-App Purchases, and submit apps for review. It also hosts the 'TestFlight' management tools.
A provisioning profile is a 'digital pass' that ties together: 1) App ID. 2) Development/Distribution certificates. 3) A list of specific device IDs (for development). It tells the iOS device that the app is authorized to run on that specific hardware.
Development Certificates allow you to run and debug the app on your own physical devices connected to your Mac. Distribution Certificates are used to sign the app for the App Store, TestFlight, or Enterprise distribution; they do not allow local debugging.
Deep Linking & Notifications20
Deep linking is a way to link to a specific piece of content inside an app rather than just launching the app's home screen. For example, a link in an email that opens a specific product page inside an e-commerce app.
Deep Links (Custom URL Schemes) use a custom protocol (e.g., `myapp://product/1`). They are easy to set up but can be hijacked by other apps. Universal Links (iOS) and App Links (Android) use standard `https://` URLs. They are more secure because they require a verified association between your website and your app via a JSON file (`apple-app-site-association` or `.well-known/assetlinks.json`).
I define an Intent Filter in the `AndroidManifest.xml` for the specific Activity. The filter must include the `<action android:name="android.intent.action.VIEW" />`, the `<category android:name="android.intent.category.BROWSABLE" />`, and the `<data>` tag specifying the scheme and host.
App Links are verified deep links using `https`. When a user clicks an App Link, the system opens the app immediately if it's installed, without showing the 'disambiguation dialog' (the 'Open with' popup). Verification is done by hosting a Digital Asset Links JSON file on your web server.
Universal Links allow an app to be launched by a standard `https` URL. If the app is not installed, the link opens the website in Safari. They provide a seamless user experience and are more secure than custom URL schemes because they prove ownership of the domain.
When the app is launched via a deep link, the link data is passed to the app's starting point. In Android, I check the `intent.data` in `onCreate()`. In iOS, I handle it in the `SceneDelegate` (via `connectionOptions`) or `AppDelegate` (via `didFinishLaunchingWithOptions`).
Dynamic Links were a Google service (now deprecated/migrating to App Links) that provided 'smart' URLs. They survived the app install process: if a user didn't have the app, the link would take them to the store, and after install, it would still take them to the specific content inside the app.
A push notification is a message sent from a server to a mobile device that appears on the home screen or in the notification center. It allows apps to communicate with users even when the app is not actively being used.
I use FCM (Firebase Cloud Messaging). I include the FCM SDK, register the device to get a token, and create a class extending `FirebaseMessagingService`. When a message arrives, I use `NotificationCompat.Builder` to show it to the user.
FCM is a cross-platform messaging solution that lets you reliably send messages at no cost. It acts as the 'bridge' between your backend server and the end-user devices, handling the complex delivery, queuing, and battery optimization logic.
I use APNs (Apple Push Notification service). 1) Request user permission via `UNUserNotificationCenter`. 2) Register for remote notifications to get a Device Token. 3) Send this token to the backend. 4) Handle the incoming payload in `userNotificationCenter(_:didReceive:withCompletionHandler:)`.
APNs is Apple's robust and efficient service for propagating information to iOS devices. Every notification sent to an Apple device must pass through APNs. It requires a specific SSL Certificate or an Auth Key (.p8 file) from the Apple Developer portal to authenticate your server.
Local Notifications are scheduled and triggered by the app itself (e.g., an alarm or a reminder). Remote Notifications are initiated by a server and delivered via FCM or APNs (e.g., a 'New Message' alert from a chat server).
In Android, I use a `PendingIntent` that defines which Activity to open. In iOS, I use the `UNUserNotificationCenterDelegate` methods. It's best practice to parse the 'extra data' (payload) in the notification to navigate the user directly to a specific screen.
Introduced in Android 8.0, channels allow you to group notifications into categories. Users can then control the behavior of each channel individually (e.g., mute 'Promotions' but keep 'Alerts' loud). Apps must assign every notification to a channel or they won't appear.
Rich notifications include images, videos, or action buttons. In Android, I use `NotificationCompat.BigPictureStyle`. In iOS, I use a Notification Service Extension to download media before the notification is displayed, or `UNNotificationAction` to add buttons.
A badge is a small number that appears on the app icon to indicate unread items. In iOS, it's set via `applicationIconBadgeNumber`. In Android, the system handles 'dots' on icons automatically based on active notifications in the status bar.
Silent (or Background) notifications wake up the app without showing a UI to the user. They are used to trigger a background data sync. In iOS, I set `content-available: 1` in the payload. In Android, I send a 'Data Message' instead of a 'Notification Message'.
Priority tells the OS how urgently the user should see the notification. 'High' priority alerts the user immediately with sound/heads-up, while 'Low' priority might only appear in the drawer without waking the screen. Excessive 'High' priority use can lead to the system throttling your app.
In iOS, I use `UNCalendarNotificationTrigger` or `UNTimeIntervalNotificationTrigger`. In Android, I use AlarmManager to trigger a `BroadcastReceiver` at a specific time, which then builds and displays the notification.
Mobile Payments15
I typically use SDKs from providers like Stripe, Braintree, or Razorpay. The SDK handles the sensitive UI (credit card entry) and tokenizes the information, sending a 'token' to my server. This ensures the app never touches raw credit card data, keeping it PCI-compliant.
Google Pay integration allows users to pay using credit/debit cards stored in their Google Account. I use the Google Pay API to request a payment token. The app sends this encrypted token to a payment processor (like Stripe) to complete the transaction. This provides a 'one-tap' checkout experience without the user manually entering card details.
Apple Pay uses the PassKit framework. The app presents a `PKPaymentAuthorizationViewController`. Once the user authenticates via FaceID/TouchID, the framework provides a secure payment token. Like Google Pay, this token is sent to the backend and then to a payment provider, ensuring the app remains PCI-compliant as it never sees the actual card number.
I use the Stripe Android/iOS SDK. 1) Create a `PaymentIntent` on the backend. 2) Initialize the Stripe 'Payment Sheet' in the app with the `client_secret`. 3) Stripe handles the UI for card entry or Apple/Google Pay. 4) The SDK returns the status of the payment directly to the app.
The PayPal Mobile SDK (now often part of the Braintree SDK) allows users to pay via their PayPal account. It provides a web-view or native transition for the user to log in and approve the payment, returning a transaction ID to the app for verification.
I use the Google Play Billing Library. 1) Define products in the Play Console. 2) Establish a connection to the BillingClient. 3) Query product details. 4) Launch the purchase flow. 5) Acknowledge the purchase on the server to prevent refunds.
It is the official library for selling digital goods and subscriptions inside Android apps. It handles the secure communication with the Google Play Store, manages the user's payment methods, and tracks the 'entitlement' (whether a user owns a feature) across multiple devices.
I use StoreKit 2. 1) Fetch products from the App Store. 2) Present a purchase UI. 3) Handle the `Product.purchase()` result. 4) Use JWS (JSON Web Signature) verification to ensure the transaction is legitimate. StoreKit 2 is the modern, async/await-based replacement for the legacy listener-based StoreKit.
StoreKit is the framework for managing in-app purchases and interactions with the App Store. It handles everything from digital product sales to requesting app reviews and managing background subscription renewals.
Never validate receipts on the device. I send the receipt data to my backend, which then communicates with Apple's or Google's validation servers. This prevents 'Man-in-the-Middle' attacks where a hacker could simulate a successful purchase signal in the app code.
A model where users pay a recurring fee (monthly/yearly) for access to content or services. Mobile developers must handle 'renewal' events, 'grace periods' (when a payment fails), and 'restoring' subscriptions when a user switches to a new device.
I catch error codes from the SDK (e.g., `UserCancelled`, `ItemUnavailable`, `PaymentDeclined`) and show user-friendly messages. For subscriptions, I use Real-time Developer Notifications (RTDN) on the server to detect failed renewals and update the user's access state in the app.
PCI DSS is a set of security standards to ensure companies that accept credit card information maintain a secure environment. Mobile apps remain compliant by using Tokenization (Stripe/Apple/Google Pay) so the app never actually processes or stores the raw 16-digit card number.
Refunds for in-app purchases are typically handled by the user through the Google Play Store or App Store support. Developers can initiate refunds via the Google Play Console or App Store Connect API, but there is usually no 'Refund' button inside the app UI for digital goods.
Tokenization replaces sensitive card data with a one-time-use 'token' (a random string). This token is useless if intercepted by hackers. The token is sent to the payment processor, who 'detokenizes' it to charge the real card, keeping the app completely out of the high-risk data loop.
Maps & Location15
1) Get an API Key from the Google Cloud Console. 2) Add the `play-services-maps` dependency. 3) Add a `SupportMapFragment` to the layout. 4) Use `getMapAsync()` to initialize the `GoogleMap` object and set markers or camera positions.
The Google Maps SDK for Android is a library that allows you to include maps based on Google Maps data in your application. It automatically handles access to Google Maps servers, data downloading, map display, and response to map gestures. It also allows you to add markers, polygons, and overlays to basic maps.
In Android, you use `googleMap.addMarker(MarkerOptions().position(latLng).title('Marker Name'))`. In iOS (MapKit), you create an `MKPointAnnotation`, set its coordinate and title, and then call `mapView.addAnnotation(annotation)`. You can further customize markers with custom icons or colors.
To draw a route, you need a list of coordinates. You send these to the Google Maps Directions API to get a polyline string. In the app, you decode this string into a list of LatLngs and use `PolylineOptions` to add the line to the map.
Geocoding is the process of converting an address (e.g., '1600 Amphitheatre Pkwy') into geographic coordinates (Latitude/Longitude). Reverse Geocoding is the opposite: converting coordinates into a human-readable address.
I use the FusedLocationProviderClient. I define a `LocationRequest` with specific intervals and priority (e.g., `PRIORITY_HIGH_ACCURACY`), check for system permissions, and then call `requestLocationUpdates()`. I handle the incoming locations in a `LocationCallback`.
It is the Google Play services location API. It is considered 'fused' because it intelligently combines signals from GPS, Wi-Fi, and cellular networks to provide the best location data while optimizing battery life. It is the recommended standard over the legacy `LocationManager`.
GPS uses satellites; it is highly accurate but slow to get a 'fix' initially, consumes more battery, and doesn't work well indoors. Network location uses Wi-Fi and Cell Tower IDs; it is faster, works indoors, and saves battery, but is significantly less accurate than GPS.
1) Import `MapKit`. 2) Add an `MKMapView` to the UI. 3) Set the delegate to handle map events. 4) Use `MKCoordinateRegion` to set the map's visible area and `MKPointAnnotation` for markers.
Core Location is the framework used to obtain the geographic location and orientation of a device. It uses all available onboard hardware, including Wi-Fi, GPS, Bluetooth, Magnetometer, Barometer, and cellular hardware to determine the user's position.
1) Declare permissions in the Manifest (Android) or Info.plist (iOS). 2) Check if permission is already granted. 3) If not, show a system dialog. For iOS, you must choose between 'When In Use' and 'Always'. For Android 10+, you must explicitly request `ACCESS_BACKGROUND_LOCATION` if needed.
Geofencing combines awareness of the user's current location with awareness of nearby points of interest. It allows the app to set up a 'virtual fence' around a coordinate. When the device enters or exits this area, the OS triggers a notification or background task for the app.
In Android, use a Foreground Service with the `location` service type to stay alive. In iOS, enable the 'Location updates' Background Mode and set `allowsBackgroundLocationUpdates` to true on the `CLLocationManager`. Both require explicit user consent for 'Always' access.
Accuracy refers to how close the reported coordinate is to the physical location. Developers can choose: High (GPS - meters), Balanced (Wi-Fi/Cell - block level), or Low/Power (Cell tower - kilometers). Higher accuracy equals higher battery drain.
1) Use the largest `minUpdateInterval` acceptable for the use case. 2) Set a `fastestInterval` to prevent the app from receiving updates more often than it can handle. 3) Reduce accuracy (e.g., use `PRIORITY_BALANCED_POWER_ACCURACY`) when the app is in the background.
Media & Camera20
1) Declare `CAMERA` permission in Manifest. 2) For simple tasks, use an Implicit Intent (`MediaStore.ACTION_IMAGE_CAPTURE`). 3) For custom camera UIs, use the CameraX library (recommended) or the legacy Camera2 API.
CameraX is a Jetpack support library designed to simplify camera development. It handles the complex differences between various device hardware and Android versions, ensuring a consistent experience. It provides high-level 'Use Cases' like Preview, Image Capture, and Image Analysis.
To capture an image, you bind an `ImageCapture` use case to your lifecycle. When the user triggers the shutter, you call `takePicture()`, providing an output file or an in-memory buffer. CameraX automatically handles rotation, flash, and focus optimization for you.
In Android, you use the `VideoCapture` use case in CameraX. In iOS, you use `AVCaptureMovieFileOutput`. Both allow you to start and stop recording to a specific file path and provide callbacks for recording status and errors.
MediaRecorder is the legacy API used to record audio and video. While still functional, it is lower-level and more difficult to manage than CameraX's VideoCapture. It requires manual setup of audio/video sources, output formats, and encoders.
I use the AVFoundation framework. 1) Create an `AVCaptureSession`. 2) Add an `AVCaptureDeviceInput` (Camera/Mic). 3) Add an `AVCapturePhotoOutput` or `AVCaptureVideoDataOutput`. 4) Display the feed via an `AVCaptureVideoPreviewLayer`.
AVFoundation is the comprehensive Apple framework for handling audio and video. It provides the tools to play, record, and edit media, as well as low-level access to camera hardware for building custom photography or scanning applications.
In Android, use the Photo Picker (modern) or `Intent.ACTION_PICK` (legacy). In iOS, use PHPickerViewController (modern) or `UIImagePickerController` (legacy). These allow the user to select photos without the app needing full permission to the entire media library.
Image compression reduces file size for network uploads. I typically use `Bitmap.compress()` in Android (to WebP or JPEG) and `UIImage.jpegData(compressionQuality:)` in iOS. This is crucial for saving user data and speeding up server-side processing.
I use Google Code Scanner (Android) or VisionKit (iOS). Both provide a system-managed UI that handles the camera, focusing, and scanning without requiring the app to ask for camera permissions, returning the decoded string directly.
Google's ML Kit is a cross-platform SDK that uses on-device machine learning to detect and decode barcodes and QR codes. It works in real-time on a camera feed and can handle multiple codes in a single frame with very high accuracy.
In Android, use `MediaPlayer` for simple files or ExoPlayer/Media3 for professional streaming. In iOS, use `AVAudioPlayer` for local files or `AVPlayer` for remote streams. Both support background playback via specific OS-level services.
ExoPlayer is an open-source, application-level media player for Android. It is more flexible and powerful than the standard `MediaPlayer`, supporting features like DASH, HLS, and SmoothStreaming, which are essential for modern video apps like YouTube or Netflix.
AVPlayer is a controller object used to manage the playback and timing of a media asset. It can play local files or remote streams and is often used with `AVPlayerViewController` to provide a native playback interface with play/pause/scrub controls.
Streaming is usually implemented via HLS (HTTP Live Streaming) or DASH. The video is broken into small segments (e.g., 10-second TS files). The player (ExoPlayer/AVPlayer) downloads these segments sequentially and adjusts the quality (Bitrate) based on the user's current internet speed.
PiP mode allows a video to continue playing in a small, floating window when the user navigates away from the app. Both Android and iOS provide APIs to 'enter' PiP mode, where the app must provide a simplified UI while the system handles the overlay window management.
I use the ScaleType (Android) or ContentMode (iOS). `CenterCrop` / `AspectFill` ensures the image fills the container without distortion (by cropping). `FitCenter` / `AspectFit` ensures the entire image is visible (adding 'letterboxing' if ratios don't match).
Adaptive Bitrate (ABR) is a technique where the video player detects changes in network bandwidth in real-time. It automatically switches between different quality levels (e.g., 480p to 1080p) during playback to prevent buffering/stalling for the user.
In Android, use `AudioRecord` (low-level) or `MediaRecorder` (high-level). In iOS, use `AVAudioRecorder`. These APIs require microphone permission and provide a way to save raw or encoded audio (AAC/M4A) to a file.
Text-to-Speech (TTS) uses the system's voice synthesis engine to 'read' text aloud. Both platforms provide native APIs (`TextToSpeech` on Android, `AVSpeechSynthesizer` on iOS) where you pass a string and a locale (language), and the system handles the audio generation.
Database & Offline10
Offline-first means the app primarily interacts with a local database (Room/Core Data). When the app fetches data from the network, it saves it locally. The UI observes the local DB. This ensures the app is always functional and provides a seamless transition when the internet goes out.
SQLite is an open-source, serverless relational database engine embedded into both Android and iOS. It stores data in a single local file. It is the foundation for higher-level libraries like Room (Android) and Core Data (iOS), providing ACID-compliant storage that is fast and reliable for structured data on mobile devices.
Syncing involves tracking 'dirty' or 'pending' records in the local database. When the device is online, a background task (WorkManager in Android or Background Tasks in iOS) pushes these local changes to the API and pulls down fresh updates. A 'timestamp' or 'versioning' strategy is used to determine which data is newer.
Conflict occurs when the same data is modified on both the device and the server. Resolution strategies include: 1) Last Write Wins: The most recent timestamp is kept. 2) Client Wins/Server Wins: Pre-defined priority. 3) Manual Merge: The user is asked to choose which version to keep (common in git or document editors).
Firebase (Realtime DB and Firestore) has a built-in feature that caches data locally. When offline, the app reads from this cache; when the app reconnects, Firebase automatically pushes all pending writes to the cloud and resolves differences, making offline support almost zero-effort for the developer.
Common strategies include: 1) Cache Aside: Try the cache; if it's a miss, fetch from DB/Network and update cache. 2) Read Through: The cache itself fetches missing data. 3) Write Through: Data is written to the cache and DB simultaneously. For mobile, 'Cache Aside' with an expiration policy is the most frequent choice.
LRU (Least Recently Used) cache is a memory-management algorithm that discards the least recently accessed items first when the cache reaches its limit. In Android, `LruCache` is often used for bitmap storage to prevent `OutOfMemoryError` while keeping frequently accessed images readily available.
In Android, I use `ConnectivityManager.NetworkCallback` to listen for real-time status changes. In iOS, I use the `NWPathMonitor`. This allows the app to show a 'No Internet' banner, pause heavy background syncs, or retry failed operations as soon as the connection is restored.
The Repository serves as a logic gate. When the ViewModel requests data, the Repository first checks the local database. It returns the cached data immediately to keep the UI responsive, then triggers a network call in the background to update the local store and the UI.
Optimistic updates involve updating the UI immediately *assuming* a network request will succeed. For example, when a user 'likes' a post, the icon turns red instantly. If the API call fails, the app 'rolls back' the UI state and shows an error message. This makes the app feel incredibly fast.
Accessibility10
Accessibility (a11y) ensures that users with disabilities—including visual, auditory, motor, or cognitive impairments—can use the app effectively. It involves supporting screen readers, providing high color contrast, and ensuring touch targets are large enough for everyone.
For ImageViews and Buttons, you use the `android:contentDescription` XML attribute. This string is read aloud by TalkBack. For purely decorative images that don't add meaning, you set the description to `@null` so the screen reader ignores them.
TalkBack is the Google screen reader included on Android devices. It gives eyes-free control of the device by using spoken feedback, allowing users to navigate through the UI using gestures like swiping and double-tapping.
1) Use meaningful labels/content descriptions. 2) Define a logical 'Focus Order' so navigation follows a natural flow. 3) Group related elements (like a label and its value) so they are read as a single unit. 4) Use 'Live Regions' to announce dynamic content changes (like an error appearing).
VoiceOver is the gesture-based screen reader for Apple devices. It describes exactly what’s happening on your screen, including text, icons, and buttons. It allows users with visual impairments to interact with the app via specialized gestures.
Dynamic Type allows users to choose their preferred text size in system settings. To support it, developers use Text Styles (e.g., `.body`, `.headline`) instead of fixed font sizes and set `adjustsFontForContentSizeCategory = true`. The UI must use Auto Layout to handle labels that grow in height.
In iOS, `accessibilityLabel` is a property of `UIAccessibility`. It provides a short, descriptive string that VoiceOver reads to describe a UI element. For example, a 'plus' button should have an accessibility label like 'Add new item'.
1) Manual Testing: Enable TalkBack/VoiceOver and navigate the app. 2) Automated Tools: Use 'Accessibility Scanner' for Android and the 'Accessibility Inspector' in Xcode. 3) Color Contrast Checkers: Ensure text is readable against its background.
According to WCAG 2.1 guidelines, the standard contrast ratio for normal text should be at least 4.5:1 against its background. For large text or icons, a ratio of 3:1 is acceptable. This ensures readability for users with low vision or color blindness.
For users with motor impairments using external keyboards, you must ensure all interactive elements can be reached using the 'Tab' key and activated using 'Enter/Space'. This requires proper focus management and avoiding 'focus traps' where a user can't navigate out of a view.
Analytics & Crashes10
Analytics are implemented by integrating an SDK (like Firebase or Mixpanel) and 'logging events' at key user actions (e.g., `logEvent('add_to_cart')`). These events can include properties like `item_id` or `price` to provide deep insights into user behavior and business health.
Firebase Analytics is a free, unlimited analytics solution that provides insight into app usage and user engagement. It automatically captures events like 'first_open' and 'session_start' and allows for custom events. It integrates with other Firebase features like Remote Config and Cloud Messaging to enable data-driven app behavior.
Specifically refers to the Google Analytics for Firebase SDK. It is designed to be 'event-centric' rather than 'page-view centric'. It allows you to define 'User Properties' (e.g., favorite genre, subscription tier) to segment your audience and analyze the lifetime value (LTV) of different user cohorts.
Events are tracked by calling the `logEvent` method of the analytics instance. You pass a unique event name and a bundle/dictionary of parameters. Best practice involves creating an Analytics Manager or a 'Tracking Plan' to ensure consistent naming conventions (e.g., using snake_case) across the whole project.
Crashlytics (part of Firebase) is a lightweight, real-time crash reporter that helps you track, prioritize, and fix stability issues. It groups crashes into 'Issues' by stack trace and provides context like device model, OS version, and 'breadcrumbs' (the last actions the user took before the crash).
Beyond automatic reporting, you can use `FirebaseCrashlytics.getInstance().log("custom message")` to add logs or `setCustomKey("state", "value")` to attach metadata. This is vital for debugging 'heisenbugs' that only happen in specific app states.
Microsoft Visual Studio App Center is an alternative to Firebase. It provides a unified platform for Build, Test, Distribute, and Analytics. Many enterprise teams use it for its robust 'Diagnostics' feature, which handles both crashes and handled errors in a very organized web dashboard.
I use Firebase Performance Monitoring. It automatically tracks 'App Start' time, 'Screen Rendering' (slow/frozen frames), and 'Network Request' success rates/latency. You can also create custom 'Traces' to measure the duration of specific code blocks like a complex database migration.
The ANR (Application Not Responding) rate is the percentage of daily active users who experienced at least one ANR. Google Play Console considers an app 'unhealthy' if the ANR rate exceeds 0.47%. Monitoring this via Android Vitals is critical for store visibility and search ranking.
This represents the percentage of unique users who did not experience a crash during a specific period. A 'Gold Standard' for mobile apps is usually 99.9% crash-free users. If this drops, it usually triggers an immediate investigation by the engineering team.
Advanced Topics20
Jetpack Compose is Android's modern, declarative UI toolkit. It replaces the traditional XML-based View system. Instead of modifying views manually, you write 'Composable' functions in Kotlin that describe what the UI should look like for a given state, significantly reducing boilerplate code.
XML is imperative (you find a view and update its property); Compose is declarative (you provide the state, and the UI re-draws itself). Compose uses a 'Single Source of Truth' state pattern, leading to fewer UI bugs and much easier code reuse through functional composition.
Declarative UI (Compose/SwiftUI) is a paradigm where the developer describes the *result* (what the UI looks like) rather than the *process* (the steps to change the UI). The framework takes care of calculating the difference and updating the screen efficiently.
KMM is an SDK that allows you to share business logic (networking, data parsing, database) across Android and iOS using a single Kotlin codebase. Unlike Flutter/React Native, it doesn't share the UI; you still write the UI using native tools (Compose/SwiftUI), ensuring a 100% native look and feel.
RN and Flutter are 'Cross-platform UI' frameworks (one UI for both). KMM is a 'Cross-platform Logic' framework. KMM allows you to keep the native performance and platform-specific UI features while only writing the complex business 'guts' once, reducing bugs and development time without compromising on the user experience.
SwiftUI is Apple’s declarative UI framework launched in 2019. It works across all Apple platforms. It uses a very concise Swift DSL to build layouts and features automatic support for Dark Mode, Accessibility, and Dynamic Type by default.
Combine is Apple's native Reactive Programming framework. It provides a declarative Swift API for processing values over time. It is the backbone of SwiftUI's state management, allowing developers to create 'Publishers' for data and 'Subscribers' to update the UI when that data changes.
RxJava is a library for composing asynchronous and event-based programs by using observable sequences. It was the industry standard for reactive programming in Android before Kotlin Coroutines and Flow were popularized. It uses a complex set of operators (map, flatMap, zip) to transform data streams.
Reactive programming is a paradigm oriented around data streams and the propagation of change. In a reactive app, the UI 'reacts' to changes in the data layer automatically. If a user's name changes in the database, the UI updates instantly without an explicit 'refresh' command from the developer.
Modularization is the practice of breaking a monolithic app into smaller, independent modules (e.g., `:feature-login`, `:core-network`, `:data-database`). This improves build speeds (only changed modules are recompiled), enables team scaling, and makes the code much more testable.
These are modules that can be downloaded on-demand from the Play Store after the initial app installation. For example, a heavy 'Camera Filters' feature could be a dynamic module that only downloads when the user first clicks on the camera icon, keeping the initial app size small.
On-demand delivery allows parts of an app's functionality to be downloaded only when needed. In Android, this is achieved through Play Feature Delivery, reducing the initial APK size. In iOS, this is similar to On-Demand Resources, where assets like game levels or large media files are fetched from the App Store only when the user reaches that stage.
Android Instant Apps allow users to use an app without installing it. When a user clicks a specific URL, a small, modularized portion of the app is downloaded and run instantly. It provides a native experience for one-off tasks like paying for parking or viewing a product from a link.
App Clips are small, lightweight versions of an iOS app (under 10MB) that can be discovered and used the moment they are needed. They are triggered via NFC tags, QR codes, or links. They allow users to complete a specific task quickly without downloading the full app from the App Store.
The NDK is a toolset that allows developers to implement parts of an Android app using native-code languages such as C and C++. It is primarily used for CPU-intensive tasks like game engines, signal processing, or physics simulations to squeeze out maximum performance.
Use native code (C/C++ via NDK) only when necessary for: 1) Performance-critical algorithms. 2) Using legacy C/C++ libraries. 3) Graphics-heavy games. For 95% of standard apps, high-level languages like Kotlin and Swift are preferred due to better memory safety and developer productivity.
JNI is the bridge that allows Java or Kotlin code running in the JVM to call and be called by native applications and libraries written in C or C++. It handles the data conversion between the managed environment and the native memory space.
A WebView is a component that renders web content (HTML/CSS/JS) directly inside a native app. While useful for displaying FAQs or Privacy Policies, relying on WebViews for core functionality usually leads to poor performance and a non-native user experience compared to actual native components.
WKWebView is the modern, high-performance replacement for the old UIWebView. It runs in its own process, supports the full Nitro JavaScript engine, and provides better security and memory management for displaying web content in iOS applications.
Hybrid development involves building an app using web technologies (HTML, CSS, JS) and wrapping it in a native container (like Cordova or Ionic). The app runs inside a WebView. This is cost-effective for simple apps but often suffers from laggy animations and limited access to hardware features.
Real-World Scenarios20
1) Use WebSockets (Socket.io) or Firebase Realtime DB for live messaging. 2) Store messages locally in Room/Core Data for offline access. 3) Use FCM/APNs for push notifications when the app is closed. 4) Implement a RecyclerView/CollectionView with a 'Reverse' LayoutManager to show new messages at the bottom.
Real-time messaging is best achieved using a persistent connection. While REST is 'pull-based,' WebSockets are 'push-based,' allowing the server to push messages to the client instantly. For massive scale, services like Ably or Pusher handle the infrastructure, including presence (online/offline status) and typing indicators.
1) Use a Single Source of Truth for the cart (Local DB synced with API). 2) Use a Repository to handle 'Add to Cart' logic. 3) Implement Stripe/Apple Pay for checkout. 4) Use Deep Links to navigate users from marketing emails directly to product pages.
1) Use a Paging Library to load images in chunks. 2) Use Glide/Kingfisher for image caching and downsampling. 3) Pre-fetch the next page of data when the user is 80% through the current list. 4) Implement Optimistic UI so likes and comments appear instantly.
I would use a Scroll Listener on the list. When the user reaches the 'threshold' (e.g., item 15 of 20), I trigger a background network request for the next 'page' of results. I then use `DiffUtil` or `reloadData` to append these items smoothly without jumping the scroll position.
In Android, I wrap the list in a `SwipeRefreshLayout`. In iOS, I add a `UIRefreshControl` to the TableView or CollectionView. Both trigger a callback where I re-fetch the latest data from the API and then call `setRefreshing(false)` or `endRefreshing()` once done.
1) Use ExoPlayer (Android) or AVPlayer (iOS). 2) Implement a Foreground Service with a notification so music keeps playing when the app is minimized. 3) Use HLS for adaptive bitrate streaming. 4) Handle Audio Focus so music pauses when the user gets a phone call.
Playlists are managed as a ordered list of 'MediaItems'. When a song finishes, the player moves to the next ID in the list. I would store the user's custom playlists in a Room DB and sync them with the backend so they are available across all their devices.
The core technology is WebRTC. 1) Use a signaling server to connect two peers. 2) Use STUN/TURN servers to bypass firewalls. 3) Implement CallKit (iOS) and ConnectionService (Android) so video calls look and behave like native phone calls.
WebRTC (Web Real-Time Communication) is an open-source project that provides mobile apps and web browsers with real-time communication via simple APIs. It handles the complex video/audio encoding, echo cancellation, and network jitter management required for high-quality calls.
When using OkHttp or URLSession, I use a custom RequestBody or URLSessionTaskDelegate that intercepts the number of bytes written. I calculate the percentage (`written / total`) and post this value to a LiveData/Observable that the UI uses to update a ProgressBar.
In Android, I use separate `strings.xml` files in localized resource folders (e.g., `values-es/` for Spanish). In iOS, I use `Localizable.strings` files. The OS automatically selects the correct file based on the user's system language. I avoid hardcoding strings in the logic to ensure the app is fully 'Internationalized' (i18n).
i18n is the process of designing an app so it can be adapted to various languages and regions without engineering changes. This includes supporting right-to-left (RTL) layouts for Arabic/Hebrew, handling different date/currency formats, and ensuring UI containers can expand for languages with longer words (like German).
I use Theme Attributes (Android) or Asset Catalog Colors (iOS). Instead of hardcoding `#FFFFFF`, I use a semantic color like `?attr/colorSurface` or `System Background Color`. The OS automatically swaps the color values when the user toggles Dark Mode in their system settings.
1) Use Google Fit API or Apple HealthKit to access step data. 2) Implement a Foreground Service with `FusedLocationProvider` for GPS path tracking. 3) Use Room/Core Data to store daily goals. 4) Use WorkManager to sync data to the server at the end of the day when the device is charging.
I use the hardware Step Counter Sensor via `SensorManager` in Android, or `CMPedometer` in iOS. These are more battery-efficient than using GPS to estimate distance, as the hardware chip counts steps even when the main CPU is asleep.
1) Customer App: Uses Google Maps for address selection. 2) Real-time Updates: Uses WebSockets or Firebase for order status (Preparing -> Out for Delivery). 3) Push Notifications: Alerts the user when the rider is nearby. 4) Payment: Integration with Stripe or digital wallets.
The driver's app sends coordinates to the backend every 5-10 seconds. The customer's app listens to these updates via a WebSocket or Firestore listener and updates a 'Rider Marker' on the map using a smooth animation (Interpolation) to prevent the marker from 'jumping'.
This requires complex location logic. 1) Geo-hashing: To find nearby drivers efficiently. 2) Google Maps Routes API: To calculate ETA and path. 3) Pub/Sub System: To handle the matching logic between rider and driver. 4) Background Location: High-accuracy tracking for the driver app even when minimized.
I use a Foreground Service with high-priority location updates. To save battery, I use the 'Balanced Power' priority when the driver is stationary and 'High Accuracy' only when moving. I batch the coordinates and send them to the server in small bursts to reduce radio wake-ups.
Debugging20
I use the Android Studio Debugger. Key features include: 1) Breakpoints: To pause execution. 2) Evaluate Expression: To test code on the fly. 3) Logcat: To view system/app logs. 4) Layout Inspector: To debug the UI hierarchy and view properties in real-time.
It's a powerful tool integrated into the IDE that lets you inspect the state of your app while it's running. It allows you to step through code line-by-line, examine variable values, and even 'Drop Frame' to rewind execution back to the start of a method.
I set a breakpoint by clicking the gutter next to a line of code. I often use Conditional Breakpoints (e.g., 'only pause if `userId == null`') to avoid stopping every time a loop runs, making it much faster to find the specific cause of a bug.
Logcat is a command-line tool that dumps a log of system messages, including stack traces when the app throws an error. I use levels like `Log.d` (debug), `Log.e` (error), and `Log.w` (warning) to filter messages and identify issues efficiently.
I use the Xcode Debugger (LLDB). I utilize the Variables View to inspect objects and the Debug Navigator to monitor CPU and Memory. I also use the Visual Debugger to 'explode' the 3D view hierarchy and find hidden or overlapping views.
LLDB is the default debugger in Xcode. It provides a command-line interface in the console where I can type commands like `po variableName` (Print Object) to inspect complex data structures or `expression variable = newValue` to change data while the app is paused.
1) Charles Proxy / Fiddler: To intercept and inspect HTTPS traffic. 2) Chucker (Android): An in-app library that shows a notification for every network request/response. 3) Network Profiler: In Android Studio or Xcode to see real-time data transfer and latency.
Charles is an HTTP proxy that runs on a computer. By pointing the mobile device to the computer's IP, I can see all incoming and outgoing requests. It is essential for verifying API payloads, simulating slow network speeds (Throttling), and 'Mapping' local JSON files to API responses for testing.
In Android, I use LeakCanary and the Memory Profiler to look for 'Abandoned' activities. In iOS, I use Instruments (Allocations & Leaks) and look for 'Retain Cycles' in the memory graph. I focus on closure captures and static references as the main culprits.
LeakCanary is a library that monitors your app's memory in debug builds. When it detects a leaked Activity or Fragment, it automatically takes a heap dump, analyzes it, and shows a notification with the 'leak trace' showing exactly which reference is keeping the object in memory.
I use the Layout Inspector (Android) or View Hierarchy Debugger (Xcode). These tools let me see a 3D representation of the UI, identify views with `zero` height/width, check if constraints are broken, and see exactly which view is intercepting touch events.
It's a tool that provides a real-time, 3D view of your app's UI hierarchy. It allows you to select any view on the screen and see its attributes (margins, padding, ID, text). It's especially useful for debugging complex ConstraintLayouts or Jetpack Compose 'Recompositions'.
Similar to Android's Layout Inspector, it captures a snapshot of the UI and allows you to rotate the screen in 3D. It helps identify 'Z-index' issues where one view is hidden behind another or when a view is correctly sized but its `clipsToBounds` property is causing it to vanish.
I use Firebase Crashlytics. I look at the 'Crash-free users' percentage and prioritize 'Issues' affecting the most users. I examine the stack trace to find the line of code that failed and use 'Breadcrumbs' to see the user's path (e.g., 'Clicked Search' -> 'Rotated Screen' -> 'Crash').
I look for the 'Caused by' line in the stack trace. It usually points to a `NullPointerException` (Android) or `EXC_BAD_ACCESS` (iOS). I then look for the first line in the trace that belongs to my app's package name to find the exact file and line number of the failure.
Symbolication is the process of translating memory addresses (hexadecimal) from a crash log back into human-readable function names and line numbers. To do this, you need the dSYM file that was generated when the app was compiled and archived.
I use Profilers to find 'bottlenecks'. I look for: 1) Long-running methods on the Main thread. 2) Frequent Garbage Collection (Memory Churn). 3) Excessive network calls. I use Trace markers to measure the time it takes for specific user actions, like 'Time to first contentful paint'.
I run the app in 'Profile Mode' (not Debug mode, as debug adds overhead). I use the CPU Profiler to record a 'Method Trace' and look for 'thick' bars in the Flame Chart which indicate slow methods that need optimization.
Method tracing is a technique where the profiler records the entry and exit time of every method called in the app. This creates a detailed map of execution, allowing me to see exactly how many milliseconds are spent in the DB vs the UI, helping me pinpoint the cause of 'jank'.
1) I check the library's GitHub 'Issues' section for similar reports. 2) I use the debugger to 'Step Into' the library code (if source is available). 3) I try to reproduce the bug in a clean, 'Sample' project to ensure the issue isn't coming from my app's configuration.
Related question banks5
React.js Questions
100 questionsComprehensive collection of the most frequently asked React JS interview questions covering fundamentals, hooks, routing, testing, and advanced concepts. Each answer is concise and interview-ready.
React Native Questions
250 questionsComprehensive guide covering React Native fundamentals, Architecture, Styling, and Component Communication. Each answer is technically rigorous for professional interviews.
Swift Questions
350 questionsDeep-dive into Swift core fundamentals, Optional safety, and ARC memory management. Essential for junior to senior level iOS roles.
Flutter Questions
200 questionsFoundational concepts covering architecture, the rendering engine, and core differences from other frameworks.
Android Questions
112 questionsComprehensive guide covering Android Fundamentals, Lifecycle, Architecture Patterns, Jetpack, Kotlin, and Security.