MainActivity.thisgetApplicationContext(), getContext(), getBaseContext()
// Доступ к ресурсам
val color = ContextCompat.getColor(this, R.color.colorPrimary)
val string = getString(R.string.app_name)
// Запуск компонентов
startActivity(Intent(this, SecondActivity::class.java))
// Доступ к SharedPreferences
val prefs = getSharedPreferences("settings", Context.MODE_PRIVATE)
// Доступ к файловой системе
val file = getFilesDir()
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d("Lifecycle", "onCreate")
// Восстановление состояния
savedInstanceState?.let {
val text = it.getString("KEY_TEXT")
binding.textView.text = text
}
}
override fun onResume() {
super.onResume()
Log.d("Lifecycle", "onResume")
// Возобновление камеры, плеера и т.д.
}
override fun onPause() {
super.onPause()
Log.d("Lifecycle", "onPause")
// Освобождение ресурсов
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString("KEY_TEXT", binding.textView.text.toString())
}
}
onConfigurationChanged()
<activity
android:name=".MainActivity"
android:configChanges="orientation|screenSize|locale"
android:label="@string/app_name" />
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
// Горизонтальная ориентация
}
}
⚠ Не рекомендуется: используйте ViewModel + onSaveInstanceState
// Пример: Activity A (запущена) -> Activity B (запущена) -> Activity C (запущена) Back Stack: [A, B, C] // Нажатие "Назад": C уничтожается ->: [A, B] B уничтожается ->: [A]
Флаги Intent: FLAG_ACTIVITY_NEW_TASK, FLAG_ACTIVITY_CLEAR_TOP, FLAG_ACTIVITY_SINGLE_TOP
android:taskAffinity в манифесте
<activity android:name=".ActivityA"
android:taskAffinity="com.example.SHARED">
<activity android:name=".ActivityB"
android:taskAffinity="com.example.PRIVATE">
// Разные affinity -> ActivityB откроется в новом стеке
Используется с FLAG_ACTIVITY_NEW_TASK для разделения контекстов
// Сохранение
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString("scroll_position", ...)
outState.putParcelableArrayList("items", list)
outState.putBoolean("is_loading", isLoading)
}
// Восстановление
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
savedInstanceState?.let {
isLoading = it.getBoolean("is_loading", false)
}
}
// ViewModel также сохраняет состояние
class FirstFragment : Fragment(R.layout.fragment_first) {
private var _binding: FragmentFirstBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentFirstBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.buttonNext.setOnClickListener {
parentFragmentManager.beginTransaction()
.replace(R.id.fragment_container, SecondFragment())
.addToBackStack(null)
.commit()
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
supportFragmentManager.beginTransaction()
.replace(R.id.container, fragment) // замена фрагмента
.add(R.id.container, fragment) // добавление
.remove(fragment) // удаление
.hide(fragment) // скрытие
.show(fragment) // показ
.detach(fragment) // отсоединение
.attach(fragment) // присоединение
.addToBackStack(null) // в back stack
.commit() // выполнить
// Фрагмент-отправитель
val result = bundleOf("selected_item" to item)
setFragmentResult("requestKey", result)
// Фрагмент-получатель
setFragmentResultListener("requestKey") { key, bundle ->
val item = bundle.getString("selected_item")
// обработать результат
}
// Ключ - уникальная строка для пары listener + sender
class ConfirmDialog : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return MaterialAlertDialogBuilder(requireContext())
.setTitle("Подтверждение")
.setMessage("Удалить элемент?")
.setPositiveButton("Да") { _, _ ->
setFragmentResult("confirm", bundleOf("deleted" to true))
}
.setNegativeButton("Отмена", null)
.create()
}
override fun onStart() {
super.onStart()
// Настройка кнопок после show()
(dialog as? AlertDialog)?.getButton(AlertDialog.BUTTON_POSITIVE)
?.setTextColor(Color.RED)
}
}
// Показ
ConfirmDialog().show(supportFragmentManager, "confirm_dialog")
BottomSheetDialogFragment
class MyBottomSheet : BottomSheetDialogFragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentBottomSheetBinding.inflate(inflater, container, false)
return binding.root
}
companion object {
const val TAG = "MyBottomSheet"
}
}
// Показ
MyBottomSheet().show(supportFragmentManager, MyBottomSheet.TAG)
// Управление состоянием из Activity
bottomSheet.behavior.state = BottomSheetBehavior.STATE_EXPANDED
bottomSheet.behavior.isHideable = true
bottomSheet.behavior.peekHeight = 200
class ViewPagerAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
override fun getItemCount(): Int = 3
override fun createFragment(position: Int): Fragment {
return when (position) {
0 -> FirstTabFragment()
1 -> SecondTabFragment()
else -> ThirdTabFragment()
}
}
}
// В Activity/Fragment
val adapter = ViewPagerAdapter(this)
binding.viewPager.adapter = adapter
TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, pos ->
tab.text = when(pos) {
0 -> "Вкладка 1"
1 -> "Вкладка 2"
else -> "Вкладка 3"
}
}.attach()
// Запуск Activity
val intent = Intent(this, SecondActivity::class.java)
intent.putExtra("KEY_DATA", "Hello")
startActivity(intent)
// Получение данных в SecondActivity
val data = intent.getStringExtra("KEY_DATA")
// Передача объекта
val user = User(1, "Иван")
intent.putExtra("USER", user) // Parcelable
// Открытие браузера
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://google.com"))
startActivity(intent)
// Отправка email
val intent = Intent(Intent.ACTION_SENDTO).apply {
data = Uri.parse("mailto:")
putExtra(Intent.EXTRA_EMAIL, arrayOf("test@mail.com"))
putExtra(Intent.EXTRA_SUBJECT, "Тема")
putExtra(Intent.EXTRA_TEXT, "Текст письма")
}
startActivity(intent)
// Разделение (share)
val intent = Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, "Поделиться текстом")
}
startActivity(Intent.createChooser(intent, "Поделиться через"))
// Уведомление с PendingIntent
val intent = Intent(this, DetailActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
this, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle("Заголовок")
.setContentText("Текст уведомления")
.setContentIntent(pendingIntent) // при нажатии
.setAutoCancel(true)
.build()
// Broadcast PendingIntent
val alarmIntent = PendingIntent.getBroadcast(
this, 0, Intent(this, AlarmReceiver::class.java),
PendingIntent.FLAG_IMMUTABLE
)
<activity android:name=".ShareActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
<data android:mimeType="image/*" />
</intent-filter>
</activity>
android:priority (от -1000 до 1000)scheme://authority/path?query#fragment Примеры: content://contacts/1 mailto:user@example.com tel:+79001234567 file:///sdcard/photo.jpg https://example.com/page?id=5
<data
android:scheme="https"
android:host="example.com"
android:pathPattern="/page/.*"
android:mimeType="text/html"
/>
// Соответствие:
// https://example.com/page/123
// https://example.com/page/abc
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https"
android:host="mysite.com"
android:pathPrefix="/app" />
</intent-filter>
// Обработка Deep Link
class DeepLinkActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val uri = intent?.data
val pageId = uri?.getQueryParameter("id")
// навигация к странице
}
}
// Сохранение
val prefs = getSharedPreferences("settings", MODE_PRIVATE)
prefs.edit()
.putString("username", "Иван")
.putInt("age", 25)
.putBoolean("dark_mode", true)
.apply() // асинхронно
// Чтение
val username = prefs.getString("username", "Гость")
val age = prefs.getInt("age", 0)
class MyDialog : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?) {
return AlertDialog.Builder(requireContext())
.setTitle("Заголовок")
.setMessage("Текст")
.setPositiveButton("OK") { _, _ -> }
.setNegativeButton("Отмена") { _, _ -> }
.create()
}
}
// Показ
MyDialog().show(supportFragmentManager, "tag")
res/Rapp/src/main/res/ ├── drawable/ # Изображения ├── layout/ # Разметки XML │ ├── activity_main.xml │ └── fragment_first.xml ├── values/ │ ├── strings.xml # Строки │ ├── colors.xml # Цвета │ ├── dimens.xml # Размеры │ └── themes.xml # Темы ├── values-night/ # Темная тема ├── mipmap/ # Иконки приложения ├── raw/ # Необработанные файлы └── xml/ # XML-конфигурации
<resources>
<string name="app_name">Мое приложение</string>
<string name="greeting">Привет, %s!</string>
<string name="html_text"><b>Жирный</b> текст</string>
</resources>
// Использование в коде
val name = getString(R.string.greeting, "Иван")
// Использование в XML
// android:text="@string/app_name"
<resources>
<color name="colorPrimary">#FF6200EE</color>
<color name="colorPrimaryVariant">#FF3700B3</color>
<color name="colorOnPrimary">#FFFFFFFF</color>
</resources>
<resources>
<dimen name="margin_small">8dp</dimen>
<dimen name="text_size_large">20sp</dimen>
</resources>
// Использование
val color = ContextCompat.getColor(this, R.color.colorPrimary)
binding.textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20f)
С 2025 года: Jetpack Compose заменяет XML-разметки.
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:weightSum="3">
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:text="1/3"
android:background="#FF0000" />
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"
android:text="2/3"
android:background="#00FF00" />
</LinearLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello!"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Нажми"
app:layout_constraintTop_toBottomOf="@id/textView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout ...>
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.3" />
<TextView
android:id="@+id/leftView"
.../
app:layout_constraintEnd_toStartOf="@id/guideline" />
<TextView
android:id="@+id/rightView"
.../
app:layout_constraintStart_toStartOf="@id/guideline" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="end"
app:constraint_referenced_ids="viewA,viewB" />
<!-- viewC привязан к барьеру -->
<Button
app:layout_constraintStart_toEndOf="@id/barrier" />
Динамическая граница по самому широкому/высокому view
<!-- spread chain -->
<Button app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<Button app:layout_constraintStart_toEndOf="@id/btn1"
app:layout_constraintEnd_toEndOf="parent" />
<!-- chain styles -->
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintHorizontal_chainStyle="packed"
Связывает views, распределяя пространство
<MotionLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutDescription="@xml/scene">
<TextView
android:id="@+id/animatedView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Двигай!" />
</MotionLayout>
// scene.xml
<Transition
motion:constraintSetStart="@id/start"
motion:constraintSetEnd="@id/end"
motion:duration="1000">
<OnSwipe />
</Transition>
class ItemAdapter(private val items: List<Item>) :
RecyclerView.Adapter<ItemAdapter.ViewHolder>() {
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val nameText: TextView = view.findViewById(R.id.nameText)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_layout, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.nameText.text = items[position].name
}
override fun getItemCount() = items.size
}
RecyclerView более эффективен, чем ListView/GridView (переиспользование View).
class ItemViewHolder(private val binding: ItemLayoutBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: Item, onClick: (Item) -> Unit) {
binding.nameText.text = item.name
binding.root.setOnClickListener { onClick(item) }
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
val binding = ItemLayoutBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
return ItemViewHolder(binding)
}
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
holder.bind(items[position]) { onItemSelected(it) }
}
val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Item>() {
override fun areItemsTheSame(old: Item, new: Item) = old.id == new.id
override fun areContentsTheSame(old: Item, new: Item) = old == new
}
class ItemListAdapter : ListAdapter<Item, ItemViewHolder>(DIFF_CALLBACK) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
val binding = ItemLayoutBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ItemViewHolder(binding)
}
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
holder.bind(getItem(position))
}
}
// Обновление данных
adapter.submitList(newItems) // асинхронный DiffUtil в фоне
val touchHelper = ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(
ItemTouchHelper.UP or ItemTouchHelper.DOWN, // drag
ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT // swipe
) {
override fun onMove(rv: RecyclerView, vh: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder): Boolean {
val from = vh.adapterPosition
val to = target.adapterPosition
Collections.swap(items, from, to)
adapter.notifyItemMoved(from, to)
return true
}
override fun onSwiped(vh: RecyclerView.ViewHolder, direction: Int) {
val pos = vh.adapterPosition
items.removeAt(pos)
adapter.notifyItemRemoved(pos)
}
override fun onChildDraw(c: Canvas, rv: RecyclerView, vh: ViewHolder,
dX: Float, dY: Float, state: Int, isActive: Boolean) {
// Кастомная отрисовка при свайпе
val bg = ColorDrawable(Color.RED)
bg.setBounds(right + dX.toInt(), top, right, bottom)
bg.draw(c)
}
})
touchHelper.attachToRecyclerView(recyclerView)
// ObjectAnimator
ObjectAnimator.ofFloat(button, "translationX", 0f, 200f).apply {
duration = 1000
start()
}
// ViewPropertyAnimator (conciliation syntax)
button.animate()
.translationX(200f)
.alpha(0.5f)
.setDuration(1000)
.start()
// ValueAnimator
ValueAnimator.ofFloat(0f, 1f).apply {
duration = 2000
addUpdateListener { animator ->
button.alpha = animator.animatedValue as Float
}
start()
}
class SyncWorker(context: Context, params: WorkerParameters)
: CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
return try {
val data = apiService.syncData()
database.save(data)
Result.success()
} catch (e: Exception) {
Result.retry()
}
}
}
// Запуск
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val workRequest = OneTimeWorkRequestBuilder<SyncWorker>()
.setConstraints(constraints)
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30, TimeUnit.SECONDS)
.build()
WorkManager.getInstance(this).enqueue(workRequest)
Критерий: нужен ли непрерывный фон? Service. Иначе - WorkManager.
// URI: content://com.example.myapp/users/123
// scheme: content
// authority: com.example.myapp
// path: /users/123
class UsersProvider : ContentProvider() {
companion object {
const val AUTHORITY = "com.example.myapp"
const val TABLE = "users"
val CONTENT_URI = Uri.parse("content://$AUTHORITY/$TABLE")
}
override fun onCreate() = true
override fun query(uri: Uri, projection: Array<String>?, selection: String?,
selectionArgs: Array<String>?, sortOrder: String?): Cursor? { ... }
override fun insert(uri: Uri, values: ContentValues?): Uri? { ... }
override fun update(uri: Uri, values: ContentValues?, sel: String?,
selArgs: Array<String>?): Int { ... }
override fun delete(uri: Uri, sel: String?, selArgs: Array<String>?): Int { ... }
override fun getType(uri: Uri): String? { ... }
}
// Чтение данных
val cursor = contentResolver.query(
UsersProvider.CONTENT_URI,
arrayOf("id", "name", "email"),
null, null, null
)
cursor?.use {
while (it.moveToNext()) {
val id = it.getInt(it.getColumnIndexOrThrow("id"))
val name = it.getString(it.getColumnIndexOrThrow("name"))
val email = it.getString(it.getColumnIndexOrThrow("email"))
}
}
// Вставка
val values = ContentValues().apply {
put("name", "Иван")
put("email", "ivan@example.com")
}
val newUri = contentResolver.insert(UsersProvider.CONTENT_URI, values)
// Удаление
contentResolver.delete(newUri!!, null, null)
// Статическая регистрация (в манифесте)
<receiver android:name=".NetworkReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>
// Динамическая регистрация (в Activity/Fragment)
val receiver = NetworkReceiver()
val intentFilter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
registerReceiver(receiver, intentFilter)
// Важно: отмена регистрации
override fun onDestroy() {
super.onDestroy()
unregisterReceiver(receiver)
}
// Отправка Ordered Broadcast
val intent = Intent("com.example.MY_ACTION")
sendOrderedBroadcast(intent, android.Manifest.permission.MY_PERM)
// Приемник с приоритетом
<receiver android:name=".FirstReceiver">
<intent-filter android:priority="100">
<action android:name="com.example.MY_ACTION" />
</intent-filter>
</receiver>
class FirstReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val data = getResultData() // получение данных от предыдущего
abortBroadcast() // остановить цепочку
setResultData("modified") // передать дальше
}
}
class DownloadService : Service() {
override fun onStartCommand(intent: Intent?,
flags: Int, startId: Int): Int {
// выполнить работу
stopSelf(startId)
return START_NOT_STICKY
}
override fun onBind(intent: Intent?)
= null // не привязан
}
private val connection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName,
service: IBinder) {
val binder = service as MyBinder
myService = binder.getService()
}
override fun onServiceDisconnected(name: ComponentName) {}
}
bindService(intent, connection, BIND_AUTO_CREATE)
// отвязка
unbindService(connection)
// Манифест (API 28+: permission)
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
class LocationService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val notification = NotificationCompat.Builder(this, "location")
.setContentTitle("Отслеживание")
.setContentText("Определение местоположения...")
.setSmallIcon(R.drawable.ic_location)
.build()
startForeground(1, notification) //必须
// запуск работы
return START_STICKY
}
override fun onBind(intent: Intent?) = null
}
@Composable аннотация
@Composable
fun Greeting(name: String) {
Text(
text = "Hello, $name!",
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier.padding(16.dp)
)
}
@Composable
fun MyApp() {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Greeting("Android")
Greeting("Compose")
Button(onClick = { /* действие */ }) {
Text("Нажми меня")
}
}
}
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Column {
Text("Счетчик: $count")
Button(onClick = { count++ }) {
Text("Добавить")
}
}
}
@Composable
fun ListExample() {
val items = remember { mutableStateListOf<String>() }
LazyColumn {
items(items) { item ->
Text(item)
}
}
Button(onClick = { items.add("Item ${items.size}") }) {
Text("Добавить")
}
}
@Composable
fun Timer() {
var seconds by remember { mutableStateOf(0) }
LaunchedEffect(Unit) {
while (true) {
delay(1000)
seconds++
}
}
Text("Прошло: $seconds сек")
}
@Composable
fun LifecycleObserver() {
val lifecycleOwner = LocalLifecycleOwner.current
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
Log.d("Lifecycle", "Event: $event")
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
}
// nav_graph.xml
<navigation
android:id="@+id/nav_graph"
app:startDestination="home">
<fragment
android:id="@+id/homeFragment"
android:name="com.example.HomeFragment"
android:label="Home">
<action
android:id="@+id/toDetail"
app:destination="@id/detailFragment" />
</fragment>
<fragment
android:id="@+id/detailFragment"
android:name="com.example.DetailFragment">
<argument
android:name="itemId"
app:argType="integer" />
</fragment>
</navigation>
// В Activity
val navController = findNavController(R.id.nav_host)
NavigationUI.setupActionBarWithNavController(this, navController)
// Запуск навигации
navController.navigate(R.id.toDetail, bundleOf("itemId" to 42))
// build.gradle
plugins {
id("androidx.navigation.safeargs.kotlin")
}
// Использование Directions (отправитель)
class HomeFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.btnDetail.setOnClickListener {
val action = HomeFragmentDirections
.toDetail(itemId = 42, title = "Заголовок")
findNavController().navigate(action)
}
}
}
// Использование Args (получатель)
class DetailFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val args = DetailFragmentArgs.fromBundle(requireArguments())
binding.titleText.text = args.title
binding.idText.text = args.itemId.toString()
}
}
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> // Permission Groups CAMERA = CAMERA LOCATION = ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION CONTACTS = READ_CONTACTS, WRITE_CONTACTS, GET_ACCOUNTS
// Проверка разрешения
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
== PackageManager.PERMISSION_GRANTED) {
// разрешено
}
// Запрос разрешения
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.CAMERA),
CAMERA_PERMISSION_CODE
)
// Обработка результата
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<String>, grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
CAMERA_PERMISSION_CODE -> {
if (grantResults.isNotEmpty()
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// разрешено
} else {
// показать объяснение
}
}
}
}
// Современный подход (Activity Result API)
val launcher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { granted ->
if (granted) { /* OK */ }
}
launcher.launch(Manifest.permission.CAMERA)
class MyLocationObserver(private val lifecycleOwner: LifecycleOwner) : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun startLocationUpdates() {
// включить GPS
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stopLocationUpdates() {
// выключить GPS
}
}
// Регистрация (устаревший подход)
lifecycle.addObserver(MyLocationObserver(this))
// Современный DefaultLifecycleObserver
class MyObserver : DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) { /* ... */ }
override fun onStop(owner: LifecycleOwner) { /* ... */ }
}
// В ViewModel - работает автоматически!
class MyViewModel : ViewModel() {
init {
viewModelScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
// запуск при STARTED
}
}
}
}
// Регистрация в Application.onCreate()
ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifecycleObserver())
// Наблюдатель
class AppLifecycleObserver : DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) {
Log.d("App", "Приложение на переднем плане")
// запуск обновлений, синхронизации
}
override fun onStop(owner: LifecycleOwner) {
Log.d("App", "Приложение на заднем плане")
// пауза, сохранение состояния
}
}
// Текущее состояние
val isForeground = ProcessLifecycleOwner.get()
.lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)
Задержка ~700ms позволяет корректно обрабатывать переходы между Activity
Нативная разработка дает максимальную производительность и доступ ко всем API платформы.