HTTP-запрос (GET и POST) в Kotlin для Android
Добавьте зависимости
В файле build.gradle (Module: app) добавьте следующие зависимости:
dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0' // Для работы с JSON
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
implementation 'com.google.code.gson:gson:2.10.1'
}
Создайте модель данных
package com.coop.myapplication1.retrofit
data class Product(
val id: Int,
val device_id: String,
val name: String,
val last_seen: String,
val is_active: Boolean
)
Определите API-интерфейс
Создайте интерфейс для описания запросов:
package com.coop.myapplication1.retrofit
import retrofit2.http.GET
import retrofit2.http.Path
interface ProductApi {
@GET("device/{id}")
suspend fun getProductById(@Path("id") id: Int): Product
}
В Main добавим следующий код:
val retrofit = Retrofit.Builder().baseUrl("http://192.168.50.70:8000/api/")
.addConverterFactory(GsonConverterFactory.create()).build()
val productApi = retrofit.create(ProductApi::class.java)
val btn = findViewById<Button>(R.id.button2)
var result = findViewById<TextView>(R.id.textView2)
btn.setOnClickListener {
CoroutineScope(Dispatchers.IO).launch {
try {
val product = productApi.getProductById(1)
runOnUiThread {
result.text = product?.name ?: "Product not found"
}
} catch (e: Exception) {
runOnUiThread {
result.text = "Error: ${e.message}"
}
}
}
}
Для современных версий Android (API 23+) при использовании HTTP (а не HTTPS) может потребоваться дополнительная настройка для разрешения небезопасных соединений, так как ваш код использует http://192.168.50.70:8000/api/. Для этого нужно:
Создать файл res/xml/network_security_config.xml:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">192.168.50.70</domain>
</domain-config>
</network-security-config>
Итоговый манифест с этой настройкой будет выглядеть так:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyApplication1"
android:networkSecurityConfig="@xml/network_security_config"
tools:targetApi="31">
<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>
</application>
</manifest>
View Binding
Как подключить View Binding
В файле build.gradle (уровень модуля) добавьте:
groovy
android {
...
buildFeatures {
viewBinding = true
}
}
В MainActivity вы подключаете View Binding так:
kotlin
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.example.myapp.databinding.ActivityMainBinding
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 = "View Binding работает!"
binding.button.setOnClickListener {
binding.textView.text = "Кнопка нажата!"
}
}
}
SharedPreferences
import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
class MainActivity : AppCompatActivity() {
private val PREFS_NAME = "MyPrefs"
private val KEY_USERNAME = "username"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Получение SharedPreferences
val prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
val editor = prefs.edit()
// Сохранение данных
editor.putString(KEY_USERNAME, "Иван")
editor.apply() // Асинхронно сохраняет данные
// Чтение данных
val username = prefs.getString(KEY_USERNAME, "Гость") // "Гость" — значение по умолчанию
println("Имя пользователя: $username")
}
}
При наличии конфликта классов:
configurations.all {
exclude(group = "com.intellij", module = "annotations")
}
Room Database
gradle
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
id("kotlin-kapt")
}
dependencies {
kapt(libs.androidx.room.compiler)
implementation(libs.androidx.room.ktx)
}
Модель данных (Entity):
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "tasks")
data class Task(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
val title: String,
val isCompleted: Boolean
)
DAO (Data Access Object):
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
@Dao
interface TaskDao {
@Insert
suspend fun insert(task: Task)
@Query("SELECT * FROM tasks")
suspend fun getAllTasks(): List<Task>
@Query("DELETE FROM tasks WHERE id = :taskId")
suspend fun deleteById(taskId: Int)
}
База данных:
import androidx.room.Database
import androidx.room.RoomDatabase
@Database(entities = [Task::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun taskDao(): TaskDao
}
Main
var db = Room.databaseBuilder(
applicationContext,
AppDatabase::class.java,
"task_database"
).build()
btn.setOnClickListener {
CoroutineScope(Dispatchers.IO).launch {
val taskDao = db.taskDao()
// Добавление задачи
taskDao.insert(Task(title = "Купить молоко", isCompleted = false))
// Получение всех задач
val tasks = taskDao.getAllTasks()
// Переход на главный поток
launch(Dispatchers.Main) {
res.text = tasks.joinToString("\n") { it.toString() }
}
}
}
Работа с JSON
implementation 'com.google.code.gson:gson:2.10.1'
Пример JSON
json
{
"id": 1,
"name": "Иван",
"age": 25
}
Модель данных:
kotlin
data class User(
val id: Int,
val name: String,
val age: Int
)
Парсинг:
kotlin
import com.google.gson.Gson
val json = """{"id": 1, "name": "Иван", "age": 25}"""
val gson = Gson()
val user = gson.fromJson(json, User::class.java)
println("Пользователь: ${user.name}, возраст: ${user.age}")
// Сериализация обратно в JSON
val jsonString = gson.toJson(user)
println("JSON: $jsonString")
Боковое меню:
package com.coop.mytestproject
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import kotlinx.coroutines.launch
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
AppNavigation()
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AppNavigation() {
val navController = rememberNavController()
val drawerState = rememberDrawerState(DrawerValue.Closed)
val scope = rememberCoroutineScope()
var selectedItem by remember { mutableStateOf(MenuItems[0]) }
ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
ModalDrawerSheet {
Column(Modifier.verticalScroll(rememberScrollState())) {
Spacer(Modifier.height(12.dp))
MenuItems.forEach { item ->
NavigationDrawerItem(
icon = { Icon(item.icon, contentDescription = null) },
label = { Text(item.name) },
selected = item == selectedItem,
onClick = {
scope.launch { drawerState.close() }
selectedItem = item
navController.navigate(item.route) {
popUpTo(navController.graph.startDestinationId)
launchSingleTop = true
}
},
modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding)
)
}
}
}
},
content = {
Scaffold(
topBar = {
TopAppBar(
title = { Text("My Project", style = TextStyle(fontSize = 20.sp)) },
navigationIcon = {
IconButton(onClick = { scope.launch { drawerState.open() } }) {
Icon(Icons.Default.Menu, contentDescription = "Menu")
}
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = Color(0xFF00BCD4),
titleContentColor = Color.White,
navigationIconContentColor = Color.White
)
)
},
content = { padding ->
NavHost(
navController = navController,
startDestination = "main",
modifier = Modifier.padding(padding)
) {
composable("main") { General() }
composable("settings") { Settings() }
composable("about") { About() }
}
}
)
}
)
}
@Composable
fun General() {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text("Main Screen", fontSize = 24.sp)
}
}
@Composable
fun About() {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text("About Screen", fontSize = 24.sp)
}
}
@Composable
fun Settings() {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text("Settings Screen", fontSize = 24.sp)
}
}
data class ItemElement(
val name: String,
val route: String,
val icon: ImageVector
)
val MenuItems: List<ItemElement> = listOf(
ItemElement("Main", "main", Icons.Default.Home),
ItemElement("Settings", "settings", Icons.Default.Settings),
ItemElement("About", "about", Icons.Default.Info)
)
build.gradle.kts(Project)
plugins {
id("com.google.devtools.ksp") version "2.0.20-1.0.24" apply false
}
База данных sqlite
build.gradle.kts(Module)
plugins {
id("com.google.devtools.ksp")
}
dependencies {
ksp("androidx.room:room-compiler:2.7.1")
implementation("androidx.compose.ui:ui:1.8.0")
implementation("androidx.compose.material:material:1.8.0")
implementation("androidx.room:room-runtime:2.7.1")
implementation("androidx.room:room-ktx:2.7.1")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7")
implementation("androidx.compose.runtime:runtime-livedata:1.8.0")
}
1. Создание сущности (Entity)
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "record")
data class MyRecord(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
val title: String,
val content: String
)
2. DAO-интерфейс
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import kotlinx.coroutines.flow.Flow
@Dao
interface RecordDao {
@Query("SELECT * FROM record")
fun getAll(): Flow<List<MyRecord>>
@Query("SELECT * FROM record WHERE id = :id")
fun findById(id: Int): MyRecord
@Update
suspend fun updateRecord(record: MyRecord)
@Insert
suspend fun insertRecord(record: MyRecord)
@Delete
suspend fun deleteRecord(record: MyRecord)
}
3. База данных Room
import android.content.Context
import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.Room
@Database(entities = [MyRecord::class], version = 1)
abstract class RecordDatabase : RoomDatabase() {
abstract fun recordDao(): RecordDao
companion object {
@Volatile private var INSTANCE: RecordDatabase? = null
fun getDatabase(context: Context): RecordDatabase {
return INSTANCE ?: synchronized(this) {
Room.databaseBuilder(
context.applicationContext,
RecordDatabase::class.java,
"record_db"
).build().also { INSTANCE = it }
}
}
}
}
4. ViewModel
TaskViewModel — это ViewModel, которая:
Получает данные из базы данных через DAO (Data Access Object).
Предоставляет данные (список задач) UI через StateFlow.
Выполняет операции добавления и удаления задач асинхронно.
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
class RecordViewModel (application: Application) : AndroidViewModel(application) {
private val dao = RecordDatabase.getDatabase(application).recordDao()
val records: StateFlow<List<MyRecord>> = dao.getAll()
.stateIn(viewModelScope, SharingStarted.Lazily, emptyList())
fun addRecord(title: String, content: String) {
viewModelScope.launch {
dao.insertRecord(MyRecord(title = title, content = content))
}
}
fun deleteRecord(record: MyRecord) {
viewModelScope.launch {
dao.deleteRecord(record)
}
}
fun updateRecord(record: MyRecord) {
viewModelScope.launch {
dao.updateRecord(record)
}
}
}
5. Compose UI
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
val navController = rememberNavController()
val context = LocalContext.current
MyNotepadAppTheme {
Scaffold(
modifier = Modifier.fillMaxSize(),
bottomBar = { BottomBar(navController) },
contentColor = Color.White,
containerColor = Color.Black
) { innerPadding ->
NavHost(navController = navController, startDestination = "home") {
composable("home") {
Home(
navController = navController,
innerPadding = innerPadding,
context = context
)
}
composable("add") { Add(innerPadding) }
composable("settings") { Settings(innerPadding, context) }
composable(
"preview/{id}/{title}/{content}",
arguments = listOf(
navArgument("id") { type = NavType.IntType },
navArgument("title") { type = NavType.StringType },
navArgument("content") { type = NavType.StringType },
)
) { backStackEntry ->
val id = backStackEntry.arguments?.getInt("id") ?: 0
val title = backStackEntry.arguments?.getString("title") ?: ""
val content = backStackEntry.arguments?.getString("content") ?: ""
MPreview(
id = id,
name = title,
content = content,
innerPadding = innerPadding
)
}
}
}
}
}
}
}
@Composable
fun Home(
navController: NavHostController,
innerPadding: PaddingValues,
viewModel: RecordViewModel = viewModel(),
context: Context = LocalContext.current
) {
var sharedPreferences = context.getSharedPreferences("MyNotes", Context.MODE_PRIVATE)
var currentPassword by rememberSaveable { mutableStateOf(sharedPreferences.getString("password", "0000").toString()) }
var inputPassword by rememberSaveable { mutableStateOf("") }
var rootPassword by rememberSaveable { mutableStateOf(sharedPreferences.getString("root", "true").toString()) }
var statusPassword by rememberSaveable { mutableStateOf(false) }
Log.d("Root", sharedPreferences.getString("root", "true").toString())
if(sharedPreferences.getString("root", "true").toBoolean()==true) {
statusPassword = true
}
if(statusPassword) {
AlertDialog(
onDismissRequest = { statusPassword = false },
title = { Text(text = "Password") },
text = {
TextField(
value = inputPassword,
onValueChange = { inputPassword = it },
modifier = Modifier.fillMaxWidth(),
label = { Text("Password") }
)},
confirmButton = {
TextButton(
onClick = {
statusPassword = false
if(inputPassword == currentPassword) {
sharedPreferences.edit { putString("root", "false") }
rootPassword = "true"
}
}
) {
Text(text = "Confirm")
}
},
dismissButton = {
TextButton(
onClick = { statusPassword = false }
) {
Text(text = "Cancel")
}
}
)
}
val records by viewModel.records.collectAsState()
Column(
Modifier
.padding(innerPadding)
.padding(start = 10.dp, end = 10.dp)
) {
LazyColumn {
items(records, key = { it.id }) { record ->
TaskItem(
navController = navController,
record = record,
onDelete = { viewModel.deleteRecord(it) }
)
}
}
}
}
@Composable
fun TaskItem(
navController: NavHostController,
record: MyRecord,
onDelete: (MyRecord) -> Unit
) {
val openDialog = remember { mutableStateOf(false) }
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp)
.clickable {
navController.navigate(
"preview/${record.id}/${
URLEncoder.encode(record.title, "UTF-8")
}/${URLEncoder.encode(record.content, "UTF-8")}"
)
},
horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically
) {
Text(text = record.title)
IconButton(onClick = { openDialog.value = true }) {
Icon(imageVector = Icons.Default.Delete, contentDescription = "Удалить")
}
}
if (openDialog.value) {
AlertDialog(
onDismissRequest = { openDialog.value = false },
title = { Text("Подтвердите удаление") },
text = { Text("Вы действительно хотите удалить запись?") },
confirmButton = {
TextButton(onClick = {
openDialog.value = false
onDelete(record)
}) {
Text("Удалить")
}
},
dismissButton = {
TextButton(onClick = { openDialog.value = false }) {
Text("Отменить")
}
}
)
}
}