Skip to content
Share
Explore

Lab 2.4: Code Walkthrough - Data Exchange Architecture Between Screens

Executive Summary

This lab completes our multi-screen application by implementing bidirectional data flow between Activities.
We demonstrate how to pass complex state information through Intent extras, enabling SecondActivity to recreate the exact visual state selected in MainActivity. This establishes patterns for data persistence across screen transitions.

Data Flow Architecture Overview

┌─────────────────────────┐ ┌─────────────────────────┐
│ MainActivity │ │ SecondActivity │
│ │ │ │
│ State Variables: │ Intent with │ Receives & Applies: │
│ • currentAnimal │ Extras Bundle │ • Animal selection │
│ • isGrayscale │ ==================> │ • Effect states │
│ • isRotated │ │ • Image resource ID │
│ │ │ │
│ [Navigate Button] ─────┼────────────────────┼──> Recreates exact │
│ │ │ visual state │
└─────────────────────────┘ └─────────────────────────┘

Detailed Data Exchange Flow

1. Preparing Data for Transmission

When the user clicks navigate, we package the current state:
private fun setupNavigationButton() {
navigateButton.setOnClickListener {
// 1. CREATE INTENT: Container for our data
val intent = Intent(this, SecondActivity::class.java)
// 2. PACKAGE STATE: Add current values as extras
intent.putExtra("SELECTED_ANIMAL", currentAnimal)
intent.putExtra("IS_GRAYSCALE", isGrayscale)
intent.putExtra("IS_ROTATED", isRotated)
// 3. RESOLVE RESOURCE: Determine which image to send
val imageResourceId = when (currentAnimal) {
"Cat" -> R.drawable.cat_image
"Dog" -> R.drawable.dog_image
"Bird" -> R.drawable.bird_image
else -> R.drawable.cat_image
}
intent.putExtra("IMAGE_RESOURCE_ID", imageResourceId)
// 4. LAUNCH WITH DATA: Send everything to SecondActivity
startActivity(intent)
}
}

Data packaging visualization:
Intent Bundle {
"SELECTED_ANIMAL": "Dog" (String)
"IS_GRAYSCALE": true (Boolean)
"IS_ROTATED": false (Boolean)
"IMAGE_RESOURCE_ID": 2131165259 (Int - R.drawable.dog_image)
}

2. Intent Extras - The Data Protocol

Let's examine each data element:
// String Extra - Animal name
intent.putExtra("SELECTED_ANIMAL", currentAnimal)
// Key: Unique identifier for retrieval
// Value: Current animal selection from radio buttons

// Boolean Extras - Effect states
intent.putExtra("IS_GRAYSCALE", isGrayscale)
intent.putExtra("IS_ROTATED", isRotated)
// Preserves checkbox states

// Int Extra - Resource identifier
intent.putExtra("IMAGE_RESOURCE_ID", imageResourceId)
// Android resource IDs are integers
// More efficient than passing image names

Why these data types?
Strings: Human-readable, flexible
Booleans: Perfect for binary states
Ints: Efficient for resource references
All are Parcelable: Can cross process boundaries

3. Data Reception in SecondActivity

SecondActivity's onCreate() now includes data extraction:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
// Find views first
findViews()
// Extract and apply received data
receiveAndDisplayData()
// Setup return navigation
setupBackButton()
}

4. Data Extraction and Application

The receiveAndDisplayData() method unpacks and applies the data:
private fun receiveAndDisplayData() {
// 1. GET INTENT: Access the Intent that started this Activity
val intent = intent // 'intent' is an Activity property
// 2. EXTRACT DATA: Retrieve each extra with default values
val selectedAnimal = intent.getStringExtra("SELECTED_ANIMAL") ?: "Unknown"
val isGrayscale = intent.getBooleanExtra("IS_GRAYSCALE", false)
val isRotated = intent.getBooleanExtra("IS_ROTATED", false)
val imageResourceId = intent.getIntExtra("IMAGE_RESOURCE_ID", R.drawable.cat_image)
// 3. APPLY IMAGE: Set the received image resource
receivedImageView.setImageResource(imageResourceId)
// 4. APPLY EFFECTS: Recreate the visual state
if (isGrayscale) {
val matrix = ColorMatrix()
matrix.setSaturation(0f)
val filter = ColorMatrixColorFilter(matrix)
receivedImageView.colorFilter = filter
}
if (isRotated) {
receivedImageView.rotation = 45f
}
// 5. UPDATE UI: Provide feedback about received data
updateInfoDisplay(selectedAnimal, isGrayscale, isRotated)
}

Data extraction pattern:
// Safe extraction with defaults
val value = intent.getTypeExtra("KEY", defaultValue)
↑ ↑ ↑
Type-specific Unique Fallback
getter method key if missing

5. Null Safety and Default Values

The extraction demonstrates defensive programming:
// String extraction with Elvis operator
val selectedAnimal = intent.getStringExtra("SELECTED_ANIMAL") ?: "Unknown"
// ↑
// If null, use "Unknown"

// Boolean extraction with default
val isGrayscale = intent.getBooleanExtra("IS_GRAYSCALE", false)
// ↑
// If not found, assume false

// Int extraction with fallback resource
val imageResourceId = intent.getIntExtra("IMAGE_RESOURCE_ID", R.drawable.cat_image)
// ↑
// Safe fallback image

6. State Recreation Logic

The effect application mirrors MainActivity's logic:
// Grayscale Application
if (isGrayscale) {
val matrix = ColorMatrix()
matrix.setSaturation(0f) // Remove all color
val filter = ColorMatrixColorFilter(matrix)
receivedImageView.colorFilter = filter
}

// Rotation Application
if (isRotated) {
receivedImageView.rotation = 45f // Apply 45-degree rotation
}

Important: Effects are conditionally applied based on received state, not toggled.

7. User Feedback Implementation

Informing the user about received data:
private fun updateInfoDisplay(animal: String, grayscale: Boolean, rotated: Boolean) {
// Build effect description
val effects = mutableListOf<String>()
if (grayscale) effects.add("Grayscale")
if (rotated) effects.add("Rotated")
val effectsText = if (effects.isEmpty()) {
"no effects"
} else {
"with ${effects.joinToString(" + ")}"
}
// Create comprehensive message
infoTextView.text = """
Received from first screen:
$animal image $effectsText
This demonstrates data passing between Activities!
""".trimIndent()
}

Data Flow Sequence Diagram

MainActivity Intent SecondActivity
│ │ │
├─[User clicks navigate] │ │
│ │ │
├─Create Intent──────────────>│ │
├─putExtra("ANIMAL", "Dog")──>│ │
├─putExtra("GRAYSCALE", true)>│ │
├─putExtra("ROTATED", false)─>│ │
├─putExtra("IMAGE_ID", 123)──>│ │
│ │ │
├─startActivity(intent)───────┼─────────────────────────────>│
│ │ │
│ │ onCreate()│
│ │ ├─getIntent()
│ │<─────────────────────────────┤
│ │ ├─getStringExtra("ANIMAL")
│ │ ├─getBooleanExtra("GRAYSCALE")
│ │ ├─getBooleanExtra("ROTATED")
│ │ ├─getIntExtra("IMAGE_ID")
│ │ │
│ │ ├─Apply image & effects
│ │ │

Intent Extras Bundle - Under the Hood

Bundle Structure:

// Conceptual representation of Intent's extras Bundle
Bundle {
entries: HashMap<String, Any> {
"SELECTED_ANIMAL" -> "Dog"
"IS_GRAYSCALE" -> true
"IS_ROTATED" -> false
"IMAGE_RESOURCE_ID" -> 2131165259
}
}

Serialization Process:

MainActivity Memory → Parcel (serialized) → Binder IPC → SecondActivity Memory
Binary format for
process boundaries

Key Architectural Patterns

1. Data Contract Pattern

We've established an implicit contract between Activities:
// Data Contract (should be documented/formalized)
object NavigationContract {
const val EXTRA_ANIMAL = "SELECTED_ANIMAL"
const val EXTRA_GRAYSCALE = "IS_GRAYSCALE"
const val EXTRA_ROTATED = "IS_ROTATED"
const val EXTRA_IMAGE_ID = "IMAGE_RESOURCE_ID"
}

2. State Transfer Pattern

// Sender (MainActivity)
private fun packageCurrentState(): Bundle {
return Bundle().apply {
putString(EXTRA_ANIMAL, currentAnimal)
putBoolean(EXTRA_GRAYSCALE, isGrayscale)
putBoolean(EXTRA_ROTATED, isRotated)
putInt(EXTRA_IMAGE_ID, resolveImageResource())
}
}

// Receiver (SecondActivity)
private fun unpackReceivedState(extras: Bundle?) {
extras?.let {
currentAnimal = it.getString(EXTRA_ANIMAL, "Unknown")
isGrayscale = it.getBoolean(EXTRA_GRAYSCALE, false)
isRotated = it.getBoolean(EXTRA_ROTATED, false)
imageResourceId = it.getInt(EXTRA_IMAGE_ID, R.drawable.cat_image)
}
}

3. Defensive Programming Pattern

Every data extraction includes fallback:
Null checks with Elvis operator
Default parameter values
Safe navigation with ?.let

Memory and Performance Considerations

Data Size Limits:

Intent extras capacity:
- Transaction buffer: ~1MB total
- Practical limit: ~500KB for safety
- Our data: ~100 bytes (negligible)

Efficient Data Types:

// GOOD: Passing resource ID (4 bytes)
intent.putExtra("IMAGE_ID", R.drawable.dog_image)

// BAD: Passing bitmap data (could be MBs)
intent.putExtra("IMAGE_BITMAP", largeBitmap) // DON'T DO THIS

Security Implications

1. Data Exposure Risk

// Our implementation (SAFE - internal only)
val intent = Intent(this, SecondActivity::class.java)

// Risky alternative (data could be intercepted)
val intent = Intent("com.example.SHOW_IMAGE")
intent.putExtra("sensitive_data", userData)

2. Input Validation

// Current: Trusting our own data
val animal = intent.getStringExtra("SELECTED_ANIMAL") ?: "Unknown"

// Production: Should validate
val animal = intent.getStringExtra("SELECTED_ANIMAL")?.let {
if (it in listOf("Cat", "Dog", "Bird")) it else "Unknown"
} ?: "Unknown"

State Consistency Challenges

Current Implementation:

MainActivity (Dog + Grayscale) → SecondActivity (Shows Dog + Grayscale)
User presses back
MainActivity (Still Dog + Grayscale) ← Returns

Potential Issues:

No bidirectional data flow
Changes in SecondActivity don't reflect back
Each Activity maintains independent state

Testing Scenarios

Data Integrity Tests:

// Test 1: All effects enabled
// MainActivity: Bird + Grayscale + Rotated
// SecondActivity: Should show rotated grayscale bird

// Test 2: No effects
// MainActivity: Cat + No effects
// SecondActivity: Should show normal cat

// Test 3: Missing data
// Manually launch SecondActivity without extras
// Should show defaults without crashing

Edge Cases:

// Null Intent extras
if (intent.extras == null) {
// Should handle gracefully with defaults
}

// Invalid resource ID
if (!isValidResourceId(imageResourceId)) {
// Fall back to safe default
}

Best Practices Demonstrated

1. Explicit Key Constants

// Better approach for production:
companion object {
private const val EXTRA_ANIMAL = "selected_animal"
private const val EXTRA_GRAYSCALE = "is_grayscale"
private const val EXTRA_ROTATED = "is_rotated"
private const val EXTRA_IMAGE_ID = "image_resource_id"
}

2. Type-Safe Extras

Using appropriate getters for each type:
getStringExtra() for Strings
getBooleanExtra() for Booleans
getIntExtra() for Integers

3. Fail-Safe Defaults

Every extraction has a sensible default preventing crashes

Future Enhancements Path

Want to print your doc?
This is not the way.
Try clicking the ··· in the right corner or using a keyboard shortcut (
CtrlP
) instead.