UNIT 3: Android Application Components & Data Management (Intermediate)
3.1. Advanced UI Components & User Interaction
RecyclerView & Adapters
-
RecyclerView vs. ListView:
| Feature | ListView | RecyclerView | | :--- | :--- | :--- | | View Recycling | Implicit, less efficient | Explicit ViewHolder pattern, mandatory | | Layout Flexibility | Only vertical/horizontal lists | Any layout via
LayoutManager(Linear, Grid, Staggered) | | Item Animations | Limited | Built-inItemAnimatorsupport | | Decoration | Simple dividers | FlexibleItemDecorationfor dividers, spacing | | Performance | Good for simple lists | Better for large/complex datasets | -
ViewHolder Pattern: A static inner class holding references to item views. Prevents repeated
findViewById()calls, drastically improving scroll performance.static class ViewHolder extends RecyclerView.ViewHolder { TextView title; ViewHolder(View itemView) { super(itemView); title = itemView.findViewById(R.id.title); } } -
Custom Adapter: Extend
RecyclerView.Adapter<ViewHolder>. Must implement:-
onCreateViewHolder(): Inflate layout & create ViewHolder. -
onBindViewHolder(): Bind data to ViewHolder views. -
getItemCount(): Return dataset size.
-
-
Item Clicks: Set listener in
onCreateViewHolder()onitemView. For long clicks, useitemView.setOnLongClickListener().
CardView & Material Design Components
-
CardView: Provides elevation (shadow) and rounded corners. Use
app:cardCornerRadius,app:cardElevationin XML. -
Material Components: Replace default widgets for consistent Material Design.
-
MaterialButton: Enhanced button with icon, stroke, ripple. -
TextInputLayout: WrapsEditTextfor floating labels, error messages, helper text. -
Snackbar: Lightweight feedback toast with optional action.
-
Fragments (Deep Dive)
-
Lifecycle Comparison:
| Activity | Fragment | | :--- | :--- | |
onCreate()|onAttach()→onCreate()| |onStart()|onCreateView()→onStart()| |onResume()|onResume()| |onPause()|onPause()| |onStop()|onStop()→onDestroyView()| |onDestroy()|onDestroy()→onDetach()| -
Fragment Transactions:
getSupportFragmentManager().beginTransaction() .add(R.id.container, fragment) // Add .replace(R.id.container, fragment) // Replace (removes previous) .remove(fragment) // Remove .addToBackStack("tag") // Adds to back stack (back button reverses) .commit(); -
Fragment Communication:
-
Interface Callbacks: Fragment defines an interface; Activity implements it.
-
ViewModel Sharing: Recommended. Same
ViewModelscoped to Activity (new ViewModelProvider(requireActivity())) shared by all Fragments.
-
-
Fragment Arguments: Pass data via
BundleusingsetArguments()before fragment is added. Retrieve inonCreate()viagetArguments().Fragment fragment = new MyFragment(); Bundle args = new Bundle(); args.putString("key", "value"); fragment.setArguments(args); -
Retained Fragments:
setRetainInstance(true)(deprecated in API 28). Fragment survives configuration changes but not process death. Use ViewModel instead.
Menus & Contextual Action Mode
-
Options Menu: Inflate XML in
onCreateOptionsMenu(). Handle clicks inonOptionsItemSelected(). -
Context Menu: Register view with
registerForContextMenu(view). Inflate inonCreateContextMenu(). Handle inonContextItemSelected(). -
ActionMode: For contextual actions (e.g., multi-select). Start with
startSupportActionMode(ActionMode.Callback).- Callback methods:
onCreateActionMode(),onPrepareActionMode(),onActionItemClicked(),onDestroyActionMode().
- Callback methods:
3.2. Data Persistence & Local Storage
SharedPreferences
-
Stores primitive types (
int,float,boolean,String) andSet<String>as key-value pairs. -
MODE_PRIVATE (default): File accessible only by app.
SharedPreferences prefs = getSharedPreferences("my_prefs", MODE_PRIVATE); prefs.edit().putString("username", "Alice").apply(); // apply() is async, commit() is sync String user = prefs.getString("username", null); -
PreferenceFragmentCompat: Provides standard Settings UI (SwitchPreference, EditTextPreference) backed by SharedPreferences.
Internal & External Storage
-
Internal Storage: Private to app. Files deleted on uninstall.
FileOutputStream fos = openFileOutput("file.txt", MODE_PRIVATE); fos.write(data.getBytes()); fos.close(); -
External Storage: Shared, requires runtime permission (
READ_EXTERNAL_STORAGE,WRITE_EXTERNAL_STORAGE). UseMediaStoreAPI for Android Q+ (scoped storage). Files may persist after uninstall. -
Cache Directories:
getCacheDir()(internal),getExternalCacheDir()(external). System may delete when low on space.
SQLite Database (Manual)
-
SQLiteOpenHelper: Manages DB creation & versioning.
public class DBHelper extends SQLiteOpenHelper { private static final String DB_NAME = "app.db"; private static final int DB_VERSION = 1; public DBHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE users (_id INTEGER PRIMARY KEY, name TEXT)"); } @Override public void onUpgrade(SQLiteDatabase db, int oldV, int newV) { db.execSQL("DROP TABLE IF EXISTS users"); onCreate(db); } } -
CRUD Operations (using
SQLiteDatabaseobjectdb):-
Insert:
ContentValues cv = new ContentValues(); cv.put("name", "Bob"); db.insert("users", null, cv); -
Query:
Cursor c = db.query("users", null, null, null, null, null, null); -
Update:
cv.put("name", "Robert"); db.update("users", cv, "_id=?", new String[]{"1"}); -
Delete:
db.delete("users", "_id=?", new String[]{"1"});
-
-
Cursor: Iterate with
while(c.moveToNext()) { String name = c.getString(c.getColumnIndex("name")); }. Always close Cursor & Database.
3.3. Room Persistence Library (Jetpack)
Core Components
-
@Entity: POJO class annotated with@Entity(tableName="users"). Fields become columns;@PrimaryKey&@ColumnInfooptional. -
@Dao: Interface with annotated methods:| Annotation | Purpose | Return Types | | :--- | :--- | :--- | |
@Insert| Insert entity/entities |void,long,List<long>| |@Query| Custom SQL (SELECT, UPDATE, DELETE) | Entity,List<Entity>,int(rows affected) | |@Update| Update entity/entities |int(rows affected) | |@Delete| Delete entity/entities |int(rows affected) | -
@Database: Abstract class extendingRoomDatabase. Lists entities & DAO version.@Database(entities = {User.class}, version = 1) public abstract class AppDatabase extends RoomDatabase { public abstract UserDao userDao(); }
Database Access & Threading
-
Instance:
Room.databaseBuilder(context, AppDatabase.class, "app.db").build(); -
Threading: Room forbids main-thread DB access by default.
-
Coroutines: DAO methods can be
suspend. Call fromDispatchers.IOorviewModelScope. -
LiveData/Flow: DAO can return
LiveData<List<User>>orFlow<List<User>>for reactive, auto-updating UI.
-
Type Converters & Relationships
-
@TypeConverter: Convert custom types to/from supported SQLite types (e.g.,Date↔Long).class Converters { @TypeConverter public static Date fromTimestamp(Long value) { return value == null ? null : new Date(value); } @TypeConverter public static Long dateToTimestamp(Date date) { return date == null ? null : date.getTime(); } } // Add to @Database annotation: `exportSchema = false, autoMigrations = {}, typeConverters = {Converters.class}` -
Relationships:
-
@Relation(One-to-Many): Define in a POJO.public class UserWithPosts { @Embedded public User user; @Relation(parentColumn = "id", entityColumn = "userId") public List<Post> posts; } -
@Embedded: Flatten object fields into parent table columns. -
Many-to-Many: Use junction entity table; define two
@Relationobjects.
-
Database Migrations
-
When schema changes (e.g., add column), increment
versionin@Databaseand provideMigrationobject.static final Migration MIGRATION_1_2 = new Migration(1, 2) { @Override public void migrate(SupportSQLiteDatabase database) { database.execSQL("ALTER TABLE users ADD COLUMN age INTEGER NOT NULL DEFAULT 0"); } }; // Add to builder: .addMigrations(MIGRATION_1_2)
3.4. Networking & Web APIs
Permissions & Security
-
Manifest:
<uses-permission android:name="android.permission.INTERNET" /> -
Runtime Permissions: For "dangerous" permissions (location, camera). Request flow:
-
Check
ContextCompat.checkSelfPermission(). -
If denied,
requestPermissions(). -
Handle result in
onRequestPermissionsResult().
-
HTTP Clients
-
Retrofit:
-
Define Interface:
public interface ApiService { @GET("users/{user}/posts") suspend fun getPosts(@Path("user") String userId, @Query("page") int page): List<Post> @POST("users") suspend fun createUser(@Body User user): Response<User> } -
Create Instance:
Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.example.com/") .addConverterFactory(GsonConverterFactory.create()) .build(); ApiService service = retrofit.create(ApiService.class);
-
-
OkHttp: Underlying client for Retrofit. Use Interceptors for logging (
HttpLoggingInterceptor), authentication headers.
Parsing Responses
-
Gson/Moshi: JSON ↔ Java/Kotlin objects. Retrofit's
GsonConverterFactory/MoshiConverterFactoryhandles automatically. -
Response Models: Create data classes matching JSON structure. Use
@SerializedNamefor mismatched keys.
Asynchronous Networking
-
Coroutines (Recommended):
viewModelScope.launch { try { val posts = withContext(Dispatchers.IO) { service.getPosts("u1", 1) } _uiState.value = posts } catch (e: Exception) { /* handle */ } } -
RxJava: Return
Observable/Singlefrom DAO/Service. UsesubscribeOn(Schedulers.io()),observeOn(AndroidSchedulers.mainThread()). -
AsyncTask: Deprecated. Avoid for new code.
3.5. Background Processing & Work Management
Threading Fundamentals
-
Main/UI Thread: Handles UI events. Never block with long operations (network, DB).
-
Background Threads: Use
Thread,ExecutorService, or higher-level APIs. -
Handler & Looper:
HandlerpostsRunnables/Messages to a thread'sLooper(message queue). Main thread has a defaultLooper.
WorkManager (Jetpack)
-
Purpose: Deferrable, guaranteed background work. Works even if app exits or device restarts. Chooses best implementation (
JobScheduler/AlarmManager/FirebaseJobDispatcher) based on API level. -
Define Worker:
public class MyWorker extends Worker { public MyWorker(@NonNull Context context, @NonNull WorkerParameters params) { super(context, params); } @NonNull @Override public Result doWork() { // Background task logic return Result.success(); // or failure(), retry() } } -
WorkRequests:
// One-time OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(MyWorker.class).build(); // Periodic (min interval 15 minutes) PeriodicWorkRequest periodic = new PeriodicWorkRequest.Builder(MyWorker.class, 1, TimeUnit.HOURS).build(); -
Constraints:
Constraints constraints = new Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .setRequiresCharging(true) .build(); OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(MyWorker.class) .setConstraints(constraints) .build(); -
Chaining:
WorkManager.getInstance(context).beginWith(workA).then(workB).then(workC).enqueue(); -
Observing:
WorkManager.getWorkInfoByIdLiveData(request.getId()).observe(this, info -> { /* info.getState() */ });
Alternatives (Contextual)
-
JobScheduler: API 21+. For batch jobs, no exact timing. -
AlarmManager: For exact timing (e.g., alarm at 8 AM). Can wake device. -
Foreground Service: Long-running task user is aware of. Must show persistent notification. Use for music playback, location tracking.
3.6. Notifications & User Engagement
NotificationCompat & Channels (O+)
-
Builder:
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID) .setSmallIcon(R.drawable.ic_notify) .setContentTitle("Title") .setContentText("Message") .setPriority(NotificationCompat.PRIORITY_DEFAULT) .setStyle(new NotificationCompat.BigTextStyle().bigText("Long text...")) .setAutoCancel(true); -
Notification Channels (Android O+): Required for Android 8.0+. Define once with importance.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel( CHANNEL_ID, "Channel Name", NotificationManager.IMPORTANCE_HIGH); channel.setDescription("Channel description"); NotificationManager manager = context.getSystemService(NotificationManager.class); manager.createNotificationChannel(channel); } -
Actions & Intents: Add action with
PendingIntentto open Activity.Intent intent = new Intent(context, MainActivity.class); PendingIntent pi = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); builder.setContentIntent(pi); -
Direct Reply: Use
RemoteInputin action.RemoteInput remoteInput = new RemoteInput.Builder("key_text_reply").setLabel("Reply").build(); NotificationCompat.Action action = new NotificationCompat.Action.Builder( R.drawable.ic_reply, "Reply", pi).addRemoteInput(remoteInput).build(); builder.addAction(action);
NotificationManager
-
NotificationManagerCompat.from(context).notify(notificationId, builder.build()); -
cancel(notificationId)to remove.
Advanced Features
-
Groups & Summaries:
setGroup()on child notifications,setGroupSummary(true)on summary notification. -
Updating: Use same
notificationIdwithnotify(). -
Cancel:
cancel(notificationId).
3.7. App Architecture & Design Patterns (Jetpack ViewModel & LiveData)
ViewModel (Jetpack)
-
Purpose: Holds UI-related data surviving configuration changes (e.g., rotation). Not for holding references to Views/Context.
-
Instantiation:
ViewModelProvider(this).get(MyViewModel.class)(Activity) orViewModelProvider(requireActivity()).get(SharedViewModel.class)(Fragment sharing). -
Lifecycle:
onCleared()called when ViewModel is no longer used (Activity/Fragment finished).
LiveData (Jetpack)
-
Lifecycle-Aware: Only notifies active (STARTED/RESUMED) observers. No memory leaks.
-
Observation:
viewModel.data.observe(this) { newData -> /* update UI */ } -
Transformations:
-
Transformations.map(sourceLiveData) { input -> transform(input) } -
Transformations.switchMap(sourceLiveData) { input -> returnAnotherLiveData }
-
-
MediatorLiveData: Add multiple sources:
mediator.addSource(source1),mediator.addSource(source2).
Repository Pattern (Conceptual)
-
Single Source of Truth: Typically Room DB as primary source.
-
Structure:
UI (Activity/Fragment) ↓ observes ViewModel (holds LiveData) ↓ calls Repository (abstracts data sources) ↓ uses Local Data Source (Room DAO) & Remote Data Source (Retrofit Service) -
Repository Code Example:
class UserRepository(private val userDao: UserDao, private val apiService: ApiService) { fun getUsers(): LiveData<List<User>> { // Could combine network & DB, e.g., refresh from network then update DB return userDao.getAll(); // DB is source of truth } suspend fun refreshUsers() { val networkUsers = apiService.getUsers() userDao.insertAll(networkUsers) // Update DB, UI observes DB } }
3.8. Debugging, Testing & Best Practices
Debugging Tools
-
Logcat &
LogClass:Log.d(TAG, "message"),Log.i(),Log.e(). Use tags to filter. -
Android Studio Debugger: Set breakpoints, step through (F8), evaluate expressions, watch variables.
-
App Inspection: View databases (Room/ SQLite), network requests (with Stetho/Chronometer), layout inspector.
Testing Fundamentals
-
Local Unit Tests (
test/source set):-
Run on JVM (no Android framework).
-
Test pure Java/Kotlin logic (ViewModel, Repository, Utils).
-
Use JUnit, Mockito (mocking), Truth/AssertJ (fluent assertions).
-
Test Room DAOs with
Robolectricor in-memory DB.
-
-
Instrumented Tests (
androidTest/source set):-
Run on device/emulator.
-
Espresso: UI testing (click, type, assert views).
onView(withId(R.id.button)).perform(click()). -
UI Automator: Cross-app UI testing.
-
Use
AndroidJUnitRunner.
-
Code Quality & Optimization
-
Memory Leaks:
-
Avoid non-static inner
Handler/Runnable(hold implicit reference to outer class). Usestatic+WeakReference. -
Don't store
Context/Viewin static fields or long-lived objects. -
Unregister listeners (broadcast, event bus) in
onDestroy().
-
-
LeakCanary: Library that automatically detects memory leaks in debug builds.
-
ProGuard / R8: Code shrinking, obfuscation, optimization. Enabled for release builds. Keep rules for reflection (Gson, Room).
-
Build Variants:
debug(debuggable, no minify) vs.release(minify enabled, signed). Configure inbuild.gradle.
[!TIP]
Exam Focus: Be prepared to draw Fragment lifecycle vs Activity, write Room Entity/DAO/DB snippets, explain ViewModel + LiveData flow, and compare WorkManager vs alternatives. Know RecyclerView adapter structure and Retrofit annotations.