Skip to content
IT-606 · Android Programming/Quick Revision Short Notes

Android Programming (IT-606) - Unit 3 Short Notes

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-in ItemAnimator support | | Decoration | Simple dividers | Flexible ItemDecoration for 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:

    1. onCreateViewHolder(): Inflate layout & create ViewHolder.

    2. onBindViewHolder(): Bind data to ViewHolder views.

    3. getItemCount(): Return dataset size.

  • Item Clicks: Set listener in onCreateViewHolder() on itemView. For long clicks, use itemView.setOnLongClickListener().

CardView & Material Design Components

  • CardView: Provides elevation (shadow) and rounded corners. Use app:cardCornerRadius, app:cardElevation in XML.

  • Material Components: Replace default widgets for consistent Material Design.

    • MaterialButton: Enhanced button with icon, stroke, ripple.

    • TextInputLayout: Wraps EditText for 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:

    1. Interface Callbacks: Fragment defines an interface; Activity implements it.

    2. ViewModel Sharing: Recommended. Same ViewModel scoped to Activity (new ViewModelProvider(requireActivity())) shared by all Fragments.

  • Fragment Arguments: Pass data via Bundle using setArguments() before fragment is added. Retrieve in onCreate() via getArguments().

    
    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 in onOptionsItemSelected().

  • Context Menu: Register view with registerForContextMenu(view). Inflate in onCreateContextMenu(). Handle in onContextItemSelected().

  • ActionMode: For contextual actions (e.g., multi-select). Start with startSupportActionMode(ActionMode.Callback).

    • Callback methods: onCreateActionMode(), onPrepareActionMode(), onActionItemClicked(), onDestroyActionMode().

3.2. Data Persistence & Local Storage

SharedPreferences

  • Stores primitive types (int, float, boolean, String) and Set<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). Use MediaStore API 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 SQLiteDatabase object db):

    • 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 & @ColumnInfo optional.

  • @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 extending RoomDatabase. 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 from Dispatchers.IO or viewModelScope.

    • LiveData/Flow: DAO can return LiveData<List<User>> or Flow<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 @Relation objects.

Database Migrations

  • When schema changes (e.g., add column), increment version in @Database and provide Migration object.

    
    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:

    1. Check ContextCompat.checkSelfPermission().

    2. If denied, requestPermissions().

    3. Handle result in onRequestPermissionsResult().

HTTP Clients

  • Retrofit:

    1. 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>
      
      }
      
      
    2. 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/MoshiConverterFactory handles automatically.

  • Response Models: Create data classes matching JSON structure. Use @SerializedName for 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/Single from DAO/Service. Use subscribeOn(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: Handler posts Runnables/Messages to a thread's Looper (message queue). Main thread has a default Looper.

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 PendingIntent to 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 RemoteInput in 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 notificationId with notify().

  • 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) or ViewModelProvider(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 & Log Class: 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 Robolectric or 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). Use static + WeakReference.

    • Don't store Context/View in 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 in build.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.

Go to where you left off?

Quick Add to Notes

Save questions, your own notes and screenshots into notes filed by unit. It takes a free account.

Create free account

Have an account? Log in