Мобильная разработка: приложение для Android
Структура Android-приложения, жизненный цикл активности, разметка и Compose, локальная база Room, запросы через Retrofit, разрешения и публикация — что нужно для курсовой.
Мобильное приложение — выигрышная тема для диплома: результат наглядно демонстрируется на защите. Разберём минимальный набор, которого хватает для полноценной работы.
Структура проекта
app/
├── src/main/
│ ├── java/com/example/app/
│ │ ├── MainActivity.kt — точка входа
│ │ ├── data/ — модели, база, сеть
│ │ ├── ui/ — экраны и компоненты
│ │ └── viewmodel/ — логика представления
│ ├── res/
│ │ ├── layout/ — XML-разметка (если не Compose)
│ │ ├── values/strings.xml — все тексты, обязательно отсюда
│ │ └── drawable/ — иконки и изображения
│ └── AndroidManifest.xml — разрешения, активности
└── build.gradle.kts — зависимости
Жизненный цикл активности
| Метод | Когда вызывается | Что делать |
|---|---|---|
| onCreate | Создание экрана | Инициализация, привязка разметки |
| onStart | Экран становится видимым | Начать обновления интерфейса |
| onResume | Экран получает фокус | Запустить анимации, датчики, камеру |
| onPause | Теряет фокус | Сохранить данные, остановить датчики |
| onStop | Становится невидимым | Освободить тяжёлые ресурсы |
| onDestroy | Уничтожение | Финальная очистка |
Ключевой момент, который спрашивают на защите: при повороте экрана активность уничтожается и создаётся заново. Данные, лежащие в её полях, теряются — поэтому состояние держат в ViewModel, которая переживает пересоздание.
class MainActivity : AppCompatActivity() {
private val viewModel: TaskViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val adapter = TaskAdapter { task -> viewModel.toggleDone(task) }
findViewById<RecyclerView>(R.id.list).adapter = adapter
// Подписка на данные: обновится сама при изменении
viewModel.tasks.observe(this) { tasks ->
adapter.submitList(tasks)
findViewById<TextView>(R.id.empty).isVisible = tasks.isEmpty()
}
findViewById<Button>(R.id.add).setOnClickListener {
val text = findViewById<EditText>(R.id.input).text.toString().trim()
if (text.isEmpty()) {
Toast.makeText(this, R.string.error_empty, Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
viewModel.add(text)
}
}
}
Локальная база данных Room
@Entity(tableName = "tasks")
data class Task(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val title: String,
val isDone: Boolean = false,
val createdAt: Long = System.currentTimeMillis()
)
@Dao
interface TaskDao {
@Query("SELECT * FROM tasks ORDER BY isDone, createdAt DESC")
fun observeAll(): Flow<List<Task>>
@Insert suspend fun insert(task: Task): Long
@Update suspend fun update(task: Task)
@Delete suspend fun delete(task: Task)
@Query("SELECT COUNT(*) FROM tasks WHERE isDone = 0")
suspend fun countPending(): Int
}
@Database(entities = [Task::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun taskDao(): TaskDao
}
Room проверяет SQL-запросы на этапе компиляции: опечатка в имени столбца не даст собрать проект. Это существенное преимущество перед прямой работой с SQLite, и его стоит упомянуть в обосновании выбора технологии.
Работа с сетью
interface ApiService {
@GET("products")
suspend fun getProducts(@Query("page") page: Int = 1): List<ProductDto>
@POST("orders")
suspend fun createOrder(@Body order: OrderRequest): OrderResponse
}
val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/v1/")
.addConverterFactory(GsonConverterFactory.create())
.build()
// Запрос в ViewModel: не блокирует интерфейс
class ProductViewModel : ViewModel() {
private val _state = MutableStateFlow<UiState>(UiState.Loading)
val state: StateFlow<UiState> = _state
fun load() = viewModelScope.launch {
_state.value = UiState.Loading
try {
val items = api.getProducts()
_state.value = UiState.Success(items)
} catch (e: IOException) {
_state.value = UiState.Error("Нет соединения")
} catch (e: HttpException) {
_state.value = UiState.Error("Ошибка сервера: ${e.code()}")
}
}
}
Jetpack Compose
@Composable
fun TaskScreen(viewModel: TaskViewModel = viewModel()) {
val tasks by viewModel.tasks.collectAsState()
var input by remember { mutableStateOf("") }
Column(modifier = Modifier.padding(16.dp)) {
Row {
OutlinedTextField(
value = input,
onValueChange = { input = it },
label = { Text(stringResource(R.string.new_task)) },
modifier = Modifier.weight(1f)
)
Button(
onClick = { viewModel.add(input); input = "" },
enabled = input.isNotBlank()
) { Text(stringResource(R.string.add)) }
}
LazyColumn {
items(tasks, key = { it.id }) { task ->
TaskRow(task, onToggle = { viewModel.toggleDone(task) })
}
}
}
}
Compose описывает интерфейс кодом вместо XML и перерисовывает только изменившиеся части. Для новой курсовой он предпочтительнее: меньше файлов, меньше шаблонного кода, и это текущий рекомендуемый подход Google.
Разрешения
// В манифесте
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
// Опасные разрешения запрашивают во время работы
private val requestPermission = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { granted ->
if (granted) openCamera()
else showExplanation()
}
requestPermission.launch(Manifest.permission.CAMERA)
- Обычные разрешения (интернет, вибрация) достаточно объявить в манифесте.
- Опасные (камера, геолокация, контакты, файлы) требуют запроса во время работы, начиная с Android 6.
- Отказ пользователя нужно обрабатывать: приложение не должно падать или зависать.
- Начиная с Android 13 уведомления тоже требуют разрешения.
Что включить в диплом
- Обоснование выбора платформы и технологий: почему Kotlin, почему Room, почему Compose.
- Диаграмма архитектуры: слои данных, представления, бизнес-логики.
- Диаграмма классов и схема локальной базы данных.
- Диаграмма вариантов использования и схема навигации между экранами.
- Скриншоты всех экранов, включая состояния загрузки и ошибки.
- Описание обработки нештатных ситуаций: нет сети, пустой список, отказ в разрешении.
- Тестирование: таблица проверок и, желательно, unit-тесты ViewModel.
- Сборка APK и инструкция по установке — проверяющий должен запустить приложение сам.
Частые вопросы
Kotlin или Java для курсовой?
Kotlin: он официально рекомендован Google, короче и безопаснее в отношении null. Java допустима, если этого требует кафедра, но новые примеры в документации выходят уже только на Kotlin.
Что делать, если нет устройства для тестирования?
Достаточно эмулятора в Android Studio. Для отчёта проверьте приложение хотя бы на двух конфигурациях экрана — это покажет корректность адаптивной вёрстки.
Нужно ли публиковать приложение в Google Play?
Для курсовой нет, публикация требует платного аккаунта и модерации. Достаточно собрать APK. Если тема связана с распространением, опишите процесс публикации теоретически.