All topics
Mobile · Learning hub

Android SDK notes for developers

Master Android SDK with a curated set of 3 developer notes — core concepts, patterns, and interview prep. Maintained by the DevRecall team.

Save this stack to your DevRecallMore Mobile notes
Android SDK

Activities, Intents & App Lifecycle

Android SDK: Activities, Intents & App Lifecycle The Android SDK provides the APIs to build Android apps. An Activity is the entry point for user interaction —

Android SDK: Activities, Intents & App Lifecycle

The Android SDK provides the APIs to build Android apps. An Activity is the entry point for user interaction — one screen. Apps are built from Activities, Services, BroadcastReceivers, and ContentProviders.

Activity Lifecycle

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // View created — initialize UI, restore state
    }

    override fun onStart() {
        super.onStart()
        // Activity visible but not interactive
    }

    override fun onResume() {
        super.onResume()
        // Activity in foreground — start animations, sensors
    }

    override fun onPause() {
        super.onPause()
        // Partially obscured — pause animations, save draft state
        // Keep it fast: next activity waits for onPause to complete
    }

    override fun onStop() {
        super.onStop()
        // Activity not visible — release heavy resources
    }

    override fun onDestroy() {
        super.onDestroy()
        // Activity finishing — final cleanup
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        outState.putString("key", "value")   // save state before rotation/kill
    }
}

Intents

Intents are messages that request an action — launching an Activity, Service, or broadcasting an event. Explicit intents name the target component; implicit intents describe the action and let the system find a handler.

// Explicit intent — launch a specific activity
val intent = Intent(this, DetailActivity::class.java)
intent.putExtra("item_id", 42)
intent.putExtra("title", "Hello")
startActivity(intent)

// Receive extras in target activity
val itemId = intent.getIntExtra("item_id", -1)
val title = intent.getStringExtra("title")

// Implicit intent — let system pick handler
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://example.com"))
startActivity(browserIntent)

val shareIntent = Intent.createChooser(
    Intent(Intent.ACTION_SEND).apply {
        type = "text/plain"
        putExtra(Intent.EXTRA_TEXT, "Check this out!")
    },
    "Share via"
)
startActivity(shareIntent)

// Start activity and get result (modern API)
val launcher = registerForActivityResult(
    ActivityResultContracts.StartActivityForResult()
) { result ->
    if (result.resultCode == RESULT_OK) {
        val data = result.data?.getStringExtra("result_key")
    }
}
launcher.launch(intent)

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- Permissions -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.CAMERA" />

    <application
        android:name=".MyApplication"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/Theme.MyApp">

        <!-- Main activity -->
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <!-- Other activities -->
        <activity android:name=".DetailActivity" android:exported="false" />

        <!-- Service -->
        <service android:name=".MyService" android:exported="false" />

        <!-- BroadcastReceiver -->
        <receiver android:name=".MyReceiver" android:exported="false">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>
    </application>
</manifest>

Runtime Permissions (Android 6+)

// Request dangerous permissions at runtime
private val requestPermissionLauncher = registerForActivityResult(
    ActivityResultContracts.RequestPermission()
) { isGranted ->
    if (isGranted) {
        accessCamera()
    } else {
        showPermissionRationale()
    }
}

fun checkAndRequestCamera() {
    when {
        ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
            == PackageManager.PERMISSION_GRANTED -> accessCamera()

        shouldShowRequestPermissionRationale(Manifest.permission.CAMERA) ->
            showRationale()  // user previously denied

        else -> requestPermissionLauncher.launch(Manifest.permission.CAMERA)
    }
}

// Multiple permissions
val launcher = registerForActivityResult(
    ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
    val cameraGranted = permissions[Manifest.permission.CAMERA] == true
    val locationGranted = permissions[Manifest.permission.ACCESS_FINE_LOCATION] == true
}
Android SDK

UI: Views, RecyclerView & Fragments

Android SDK: UI Views, RecyclerView & Fragments View Binding // build.gradle: viewBinding { enabled = true } class MainActivity : AppCompatActivity() { private

Android SDK: UI Views, RecyclerView & Fragments

View Binding

// build.gradle: viewBinding { enabled = true }

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.textView.text = "Hello"
        binding.button.setOnClickListener {
            binding.textView.text = "Clicked!"
        }
    }
}

// In Fragments
class MyFragment : Fragment(R.layout.fragment_my) {
    private var _binding: FragmentMyBinding? = null
    private val binding get() = _binding!!

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        _binding = FragmentMyBinding.bind(view)
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null   // avoid memory leak
    }
}

RecyclerView

// Adapter
class ItemAdapter(
    private val items: List<String>,
    private val onItemClick: (String) -> Unit
) : RecyclerView.Adapter<ItemAdapter.ViewHolder>() {

    inner class ViewHolder(val binding: ItemRowBinding) :
        RecyclerView.ViewHolder(binding.root)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val binding = ItemRowBinding.inflate(
            LayoutInflater.from(parent.context), parent, false
        )
        return ViewHolder(binding)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val item = items[position]
        holder.binding.textView.text = item
        holder.binding.root.setOnClickListener { onItemClick(item) }
    }

    override fun getItemCount() = items.size
}

// In Activity/Fragment
binding.recyclerView.apply {
    layoutManager = LinearLayoutManager(context)
    adapter = ItemAdapter(listOf("Item 1", "Item 2")) { item ->
        Toast.makeText(context, item, Toast.LENGTH_SHORT).show()
    }
}

// DiffUtil for efficient updates
class ItemDiffCallback(
    private val oldList: List<String>,
    private val newList: List<String>
) : DiffUtil.Callback() {
    override fun getOldListSize() = oldList.size
    override fun getNewListSize() = newList.size
    override fun areItemsTheSame(old: Int, new: Int) = oldList[old] == newList[new]
    override fun areContentsTheSame(old: Int, new: Int) = oldList[old] == newList[new]
}

Fragments

// Fragment definition
class DetailFragment : Fragment(R.layout.fragment_detail) {
    private val args: DetailFragmentArgs by navArgs()  // Navigation Safe Args

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // Access args.itemId, args.title
    }

    companion object {
        fun newInstance(id: Int): DetailFragment {
            return DetailFragment().apply {
                arguments = bundleOf("item_id" to id)
            }
        }
    }
}

// Fragment transactions (without Navigation Component)
supportFragmentManager.commit {
    setReorderingAllowed(true)
    replace(R.id.fragment_container, DetailFragment.newInstance(42))
    addToBackStack(null)
}

// Fragment result API (pass data back to parent fragment)
// Parent fragment:
childFragmentManager.setFragmentResultListener("requestKey", viewLifecycleOwner) { _, bundle ->
    val result = bundle.getString("resultKey")
}

// Child fragment:
parentFragmentManager.setFragmentResult("requestKey",
    bundleOf("resultKey" to "value")
)
Android SDK

Services, Permissions & Background Work

Android SDK: Services, Permissions & Background Work WorkManager WorkManager is the recommended API for deferrable, guaranteed background work. It survives app

Android SDK: Services, Permissions & Background Work

WorkManager

WorkManager is the recommended API for deferrable, guaranteed background work. It survives app restarts and works across Android versions.

// Add dependency: implementation("androidx.work:work-runtime-ktx:2.9.0")

// Define work
class UploadWorker(context: Context, params: WorkerParameters) :
    CoroutineWorker(context, params) {

    override suspend fun doWork(): Result {
        val fileUri = inputData.getString("file_uri") ?: return Result.failure()
        return try {
            uploadFile(fileUri)
            Result.success()
        } catch (e: Exception) {
            if (runAttemptCount < 3) Result.retry() else Result.failure()
        }
    }
}

// Schedule work
val uploadRequest = OneTimeWorkRequestBuilder<UploadWorker>()
    .setInputData(workDataOf("file_uri" to "content://..."))
    .setConstraints(
        Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .setRequiresBatteryNotLow(true)
            .build()
    )
    .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30, TimeUnit.SECONDS)
    .build()

WorkManager.getInstance(context).enqueue(uploadRequest)

// Periodic work
val syncRequest = PeriodicWorkRequestBuilder<SyncWorker>(15, TimeUnit.MINUTES)
    .build()
WorkManager.getInstance(context)
    .enqueueUniquePeriodicWork("sync", ExistingPeriodicWorkPolicy.KEEP, syncRequest)

// Observe status
WorkManager.getInstance(context)
    .getWorkInfoByIdLiveData(uploadRequest.id)
    .observe(this) { info ->
        if (info?.state == WorkInfo.State.SUCCEEDED) showSuccess()
    }

Foreground Services

// For long-running tasks visible to user (music playback, navigation, upload)
class MusicService : Service() {

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val notification = buildNotification()
        startForeground(NOTIFICATION_ID, notification)  // must call within 5s
        // do work...
        return START_STICKY
    }

    override fun onBind(intent: Intent?) = null

    private fun buildNotification(): Notification {
        val channel = NotificationChannel(
            "music_channel", "Music Playback", NotificationManager.IMPORTANCE_LOW
        )
        getSystemService(NotificationManager::class.java).createNotificationChannel(channel)

        return NotificationCompat.Builder(this, "music_channel")
            .setContentTitle("Now Playing")
            .setSmallIcon(R.drawable.ic_music)
            .build()
    }
}

// Start from Activity
val serviceIntent = Intent(this, MusicService::class.java)
ContextCompat.startForegroundService(this, serviceIntent)

BroadcastReceiver

// Dynamic receiver (registered in code, active while app runs)
class NetworkReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        val isConnected = cm.activeNetwork != null
        // handle connectivity change
    }
}

// Register/unregister with lifecycle
private lateinit var receiver: NetworkReceiver
override fun onResume() {
    super.onResume()
    receiver = NetworkReceiver()
    registerReceiver(receiver, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION))
}
override fun onPause() {
    super.onPause()
    unregisterReceiver(receiver)
}

// Send local broadcast (within app only)
LocalBroadcastManager.getInstance(this)
    .sendBroadcast(Intent("com.example.CUSTOM_ACTION"))

Key Jetpack Libraries

  • ViewModel + LiveData/StateFlow: survive configuration changes, hold UI state.

  • Room: SQLite ORM with compile-time query verification, Flow support, migrations.

  • Navigation Component: type-safe navigation graph, back stack management, deep links.

  • DataStore: replaces SharedPreferences — coroutine-based, type-safe (Preferences or Proto).

  • Hilt: dependency injection built on Dagger, first-class Android support.

  • Retrofit + OkHttp: HTTP client with Kotlin coroutine adapters.

  • Coil: coroutine-based image loading, Compose support.

  • Paging 3: paginated data loading from network/database with RecyclerView or Compose integration.

Keep your Android SDK knowledge sharp.

Save this stack to your personal DevRecall — add your own notes, track what you're learning, and share what you know with the community.

Get started — free forever