/r/androiddev
News for Android developers with the who, what, where, when and how of the Android community.
News / Articles / Talks / Tools / Open source!
News for Android app developers with the who, what, where, when, and how of the Android community. Probably mostly the how.
Here, you'll find:
This sub-reddit isn't about phones' and apps' general functionality, support, or system software development (ROMs). For news and questions about these topics try using other subs like
Starting Android career in 2022
Android Job Interview Questions and Answers
App Portfolio Ideas, Tiered List
Autoposted at approx 9AM EST / 2PM GMT
/r/androiddev
Bug @Composable invocations can only happen from the context of a @Composable function 77, in the part=
// Llamar a la función de búsqueda en un coroutine
LaunchedEffect(query) {
try {
searchResults = search(query) // Llama a la función search
} catch (e: Exception) {
errorMessage = "Error al buscar: ${e.message}"
} finally {
isLoading = false
}
}
Code=
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Search
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.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.barratrasparente1.R
import com.example.barratrasparente1.ui.theme.BarraTrasparente1Theme
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.GET
import retrofit2.http.Query
import androidx.compose.foundation.verticalScroll
import androidx.compose.foundation.rememberScrollState
import androidx.navigation.NavHostController
import androidx.compose.foundation.lazy.items
import androidx.navigation.compose.rememberNavController
import android.util.Log
import androidx.compose.material.icons.filled.ArrowForward
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.room.util.query
import com.example.barratrasparente1.SearchNavegacion.ArtistAlbumsScreen
@Composable
fun SearchPage(navController: NavHostController) {
var artistName by remember { mutableStateOf("") }
var results by remember { mutableStateOf<List<String>>(emptyList()) }
var isSearchActive by remember { mutableStateOf(false) }
var albums by remember { mutableStateOf<List<Album>>(emptyList()) }
var tracks by remember { mutableStateOf<List<Track>>(emptyList()) }
var currentlyPlayingTrack by remember { mutableStateOf<Track?>(null) }
var artistResults by remember { mutableStateOf<List<Artist>>(emptyList()) }
var trackResults by remember { mutableStateOf<List<Track>>(emptyList()) }
var isLoading by remember { mutableStateOf(false) }
var errorMessage by remember { mutableStateOf<String?>(null) }
var searchResults by remember { mutableStateOf<List<SearchResult>>(emptyList()) }
Box(modifier = Modifier.fillMaxSize()) {
Image(
painter = painterResource(id = R.drawable.background),
contentDescription = null,
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop
)
currentlyPlayingTrack?.let { track ->
Text(
text = "Reproduciendo: ${track.name} - ${track.artist}",
color = Color.White,
modifier = Modifier.padding(16.dp)
)
}
// Usar Column para organizar la barra de búsqueda y los resultados
Column(modifier = Modifier.fillMaxSize()) {
// Barra de búsqueda
TransparentSearchBarExample(
onSearch = { query ->
artistName = query
isSearchActive = true
// Llamar a la función de búsqueda en un coroutine
LaunchedEffect(query) {
try {
searchResults = search(query) // Llama a la función search
} catch (e: Exception) {
errorMessage = "Error al buscar: ${e.message}"
} finally {
isLoading = false
}
}
},
onClose = {
artistName = ""
results = emptyList()
isSearchActive = false
albums = emptyList()
tracks = emptyList()
currentlyPlayingTrack = null
}
)
// Realiza la búsqueda solo si la búsqueda está activa
// Realiza la búsqueda
if (isSearchActive) {
SearchArtist(
query = artistName,
searchType = SearchType.ARTIST,
onResultsUpdated = { newResults ->
results = newResults
},
onArtistClick = { selectedArtist ->
// Aquí puedes buscar álbumes y pistas relacionadas
navController.navigate("artistAlbums/$selectedArtist")
},
onTrackClick = { track ->
// Maneja la reproducción de la canción
currentlyPlayingTrack = track
}
)
}
// Carga de fondo y mensajes
if (isLoading) {
CircularProgressIndicator(color = Color.White)
} else {
errorMessage?.let {
Text(text = it, color = Color.Red)
}
}
// Mostrar resultados en una lista deslizante solo si la búsqueda está activa
if (isSearchActive) {
LazyColumn {
if (searchResults.isNotEmpty()) {
// Encabezado de resultados de búsqueda
//if (artistResults.isNotEmpty() || albums.isNotEmpty() || tracks.isNotEmpty()) {
item {
Text(
text = "Resultados de búsqueda",
modifier = Modifier.padding(16.dp),
color = Color.White
)
}
}
/*
// Resultados de artistas
items(results) { artist ->
ArtistItem(artistName = artist) {
Log.d("ArtistItem", "Navegando a álbumes de: $artist")
navController.navigate("artistAlbums/$artist")
}
}
// Mostrar resultados de artistas
items(albums) { album ->
AlbumItem(album)
}*/
/*
// Resultados de artistas
if (artistResults.isNotEmpty()) {
items(artistResults) { artist ->
ArtistItem(artistName = artist.name) {
navController.navigate("artistAlbums/${artist.name}")
}
}
}
// Encabezado de álbumes
if (albums.isNotEmpty()) {
item {
Text(
text = "Álbumes",
modifier = Modifier.padding(16.dp),
color = Color.White
)
}
items(albums) { album ->
AlbumItem(album) {
navController.navigate("albumDetails/${album.name}")
}
}
}
// Encabezado de pistas
if (trackResults.isNotEmpty()) {
item {
Text(
text = "Pistas",
modifier = Modifier.padding(16.dp),
color = Color.White
)
}
items(trackResults) { track ->
TrackItem(track) {
currentlyPlayingTrack = track
}
}
}*/
// Mostrar resultados
items(searchResults) { result ->
when (result) {
is SearchResult.ArtistResult -> {
ArtistItem(artistName = result.artist.name) {
navController.navigate("artistAlbums/${result.artist.name}")
}
}
is SearchResult.AlbumResult -> {
AlbumItem(album = result.album) {
navController.navigate("albumDetails/${result.album.name}")
}
}
is SearchResult.TrackResult -> {
TrackItem(track = result.track) {
currentlyPlayingTrack = result.track
}
}
}
}
if (searchResults.isEmpty()) {
// Mensaje si no hay resultados
// if (results.isEmpty() && albums.isEmpty() && tracks.isEmpty()) {
item {
Text(
"No se encontraron resultados",
modifier = Modifier.padding(16.dp),
color = Color.White
)
}
}
}
}
}
}
}
enum class SearchType {
ARTIST,
ALBUM,
TRACK
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TransparentSearchBarExample(onSearch: (String) -> Unit, onClose: () -> Unit) {
val textFieldState = remember { mutableStateOf("") }
var expanded by remember { mutableStateOf(false) }
Log.d("TransparentSearchBarExample", "Estado inicial de la barra de búsqueda: expandido = $expanded") // Log del estado inicial
SearchBar(
modifier = Modifier.fillMaxWidth(),
inputField = {
SearchBarDefaults.InputField(
query = textFieldState.value,
onQueryChange = { newQuery ->
textFieldState.value = newQuery
Log.d("TransparentSearchBarExample", "Consulta actualizada: $newQuery") // Log de cambio de consulta
},
onSearch = {
// Llama a onSearch y cierra las sugerencias
onSearch(textFieldState.value)
expanded = false // Cierra las sugerencias
Log.d("TransparentSearchBarExample", "Búsqueda realizada: ${textFieldState.value}") // Log de búsqueda
},
expanded = expanded,
onExpandedChange = { newExpanded ->
expanded = newExpanded
Log.d("TransparentSearchBarExample", "Estado de expansión cambiado: $expanded") // Log de cambio de expansión
},
placeholder = { Text("Buscar...", color = Color.Gray) },
leadingIcon = {
Icon(
imageVector = Icons.Default.Search,
contentDescription = null,
tint = Color.White
)
},
trailingIcon = {
IconButton(onClick = {
textFieldState.value = "" // Limpiar el texto
expanded = false // Colapsar la barra de búsqueda
onClose()
Log.d("TransparentSearchBarExample", "Barra de búsqueda cerrada y texto limpiado") // Log de cierre
}) {
Icon(Icons.Default.Close, contentDescription = "Cerrar")
}
},
colors = TextFieldDefaults.colors(
focusedContainerColor = Color.Transparent,
unfocusedContainerColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
focusedTextColor = Color.White,
unfocusedTextColor = Color.White,
)
)
},
expanded = expanded,
onExpandedChange = { newExpanded ->
expanded = newExpanded
Log.d("TransparentSearchBarExample", "Estado de expansión cambiado: $expanded") // Log de cambio de expansión
},
colors = SearchBarDefaults.colors(
containerColor = Color.Transparent
)
) {
// Muestra sugerencias si es necesario
Column {
repeat(5) { index ->
ListItem(
headlineContent = { Text("Sugerencia $index") },
modifier = Modifier.clickable {
textFieldState.value = "Sugerencia $index"
expanded = false // Cierra las sugerencias al seleccionar
onSearch(textFieldState.value) // Realiza la búsqueda
Log.d("TransparentSearchBarExample", "Sugerencia seleccionada: Sugerencia $index") // Log de sugerencia seleccionada
}
)
}
}
}
}
@Composable
fun SearchArtist(
query: String,
searchType: SearchType,
onResultsUpdated: (List<String>) -> Unit,
onArtistClick: (String) -> Unit,
onTrackClick: (Track) -> Unit
) {
val scope = rememberCoroutineScope()
var artistResults by remember { mutableStateOf<List<Artist>>(emptyList()) }
var trackResults by remember { mutableStateOf<List<Track>>(emptyList()) }
var albumResults by remember { mutableStateOf<List<Album>>(emptyList()) }
var isLoading by remember { mutableStateOf(false) }
var errorMessage by remember { mutableStateOf<String?>(null) }
LaunchedEffect(query) {
if (query.isNotEmpty()) {
isLoading = true
errorMessage = null
try {
when (searchType) {
SearchType.ARTIST -> {
artistResults = RetrofitInstance.api.searchArtist(artist = query).results.artistmatches.artist
onResultsUpdated(artistResults.map { it.name })
}
SearchType.TRACK -> {
trackResults = RetrofitInstance.api.searchTrack(track = query).results.trackmatches.track
onResultsUpdated(trackResults.map { it.name })
}
SearchType.ALBUM -> {
albumResults = RetrofitInstance.api.searchAlbum(album = query).results.albummatches.album // Asegúrate de que esto esté aquí
onResultsUpdated(albumResults.map { it.name }) // Actualiza los resultados para álbumes
}
}
} catch (e: Exception) {
errorMessage = "Error al buscar: ${e.message}"
} finally {
isLoading = false
}
}
}
}
@Composable
fun SearchResults(results: List<String>, onArtistClick: (String) -> Unit) {
Log.d("SearchResults", "Resultados a mostrar: $results") // Log de resultados a mostrar
if (results.isEmpty()) {
Log.d("SearchResults", "No se encontraron resultados") // Log si no hay resultados
Text("No se encontraron resultados", color = Color.White)
} else {
LazyColumn {
items(results) { artistName ->
ArtistItem(artistName = artistName) {
Log.d("SearchResults", "Artista seleccionado: $artistName") // Log de artista seleccionado
onArtistClick(artistName) // Llama a la función cuando se selecciona un artista
}
}
}
}
}
@Composable
fun ArtistItem(artistName: String, onArtistClick: () -> Unit) {
Text(
text = artistName,
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onArtistClick) // Ejecuta onArtistClick cuando se hace clic
.padding(16.dp),
color = Color.White,
style = MaterialTheme.typography.body1
)
}
// Modifica TrackItem para incluir el manejo de clics
@Composable
fun TrackItem(track: Track, onClick: () -> Unit) {
ListItem(
modifier = Modifier.clickable(onClick = onClick),
headlineContent = { Text(track.name) },
supportingContent = { Text("Artista: ${track.artist}") }
)
}
@Composable
fun AlbumItem(album: Album, onClick: () -> Unit) {
ListItem(
modifier = Modifier.clickable(onClick = onClick),
headlineContent = { Text(album.name) },
supportingContent = { Text("Artista: ${album.artist}") }
)
}
suspend fun search(query: String): List<SearchResult> {
val musicRepository = MusicRepository(RetrofitInstance.api)
val artistResults = musicRepository.searchArtists(query)
val trackResults = musicRepository.searchTracks(query)
val albumResults = musicRepository.searchAlbums(query)
val combinedResults = mutableListOf<SearchResult>()
combinedResults.addAll(artistResults.map { SearchResult.ArtistResult(it) })
combinedResults.addAll(trackResults.map { SearchResult.TrackResult(it) })
combinedResults.addAll(albumResults.map { SearchResult.AlbumResult(it) })
return combinedResults
}
class MusicRepository(private val api: LastFmApi) {
suspend fun searchArtists(query: String): List<Artist> {
return api.searchArtist(artist = query).results.artistmatches.artist
}
suspend fun searchTracks(query: String): List<Track> {
return api.searchTrack(track = query).results.trackmatches.track
}
suspend fun searchAlbums(query: String): List<Album> {
return api.searchAlbum(album = query).results.albummatches.album
}
suspend fun getArtistAlbums(artist: String): List<Album> {
return api.getArtistAlbums(artist = artist).results.albummatches.album
}
}
sealed class SearchResult {
data class ArtistResult(val artist: Artist) : SearchResult()
data class AlbumResult(val album: Album) : SearchResult()
data class TrackResult(val track: Track) : SearchResult()
}
// Clases para la respuesta JSON de la API de Last.fm
data class ArtistSearchResponse(val results: Results)
data class Results(val artistmatches: ArtistMatches)
data class ArtistMatches(val artist: List<Artist>)
data class Artist(val name: String, val url: String)
data class TrackSearchResponse(val results: TrackResults)
data class TrackResults(val trackmatches: TrackMatches)
data class TrackMatches(val track: List<Track>)
data class Track(val name: String, val url: String, val artist: String)
data class AlbumSearchResponse(val results: AlbumResults)
data class AlbumResults(val albummatches: AlbumMatches)
data class AlbumMatches(val album: List<Album>)
data class Album(val name: String, val url: String, val artist: String)
// Interfaz para las solicitudes de la API
interface LastFmApi {
@GET("2.0/")
suspend fun searchArtist(
@Query("method") method: String = "artist.search",
@Query("artist") artist: String,
@Query("api_key") apiKey: String = "5a21bfe4c62ad0b6dc214546e23f2698",
@Query("format") format: String = "json"
): ArtistSearchResponse
@GET("2.0/")
suspend fun searchTrack(
@Query("method") method: String = "track.search",
@Query("track") track: String,
@Query("api_key") apiKey: String = "5a21bfe4c62ad0b6dc214546e23f2698",
@Query("format") format: String = "json"
): TrackSearchResponse
@GET("2.0/")
suspend fun searchAlbum(
@Query("method") method: String = "album.search",
@Query("album") album: String,
@Query("api_key") apiKey: String = "5a21bfe4c62ad0b6dc214546e23f2698",
@Query("format") format: String = "json"
): AlbumSearchResponse
@GET("2.0/")
suspend fun getArtistAlbums(
@Query("method") method: String = "artist.getAlbums",
@Query("artist") artist: String,
@Query("api_key") apiKey: String = "5a21bfe4c62ad0b6dc214546e23f2698",
@Query("format") format: String = "json"
): AlbumSearchResponse
}
// Configuración de Retrofit
object RetrofitInstance {
private const val BASE_URL = "https://ws.audioscrobbler.com/"
val api: LastFmApi = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(LastFmApi::class.java)
}
@Preview(showBackground = true)
@Composable
fun PreviewSearchPage() {
val navController = rememberNavController() // Crea un NavHostController simulado
BarraTrasparente1Theme {
SearchPage(navController = navController) // Pasa el navController a SearchPage
}
}
Hi everyone. I need to make my app to allow HTTP traffic and self signed certificates because it has to he able to connect to home servers that not always have proper HTTPS certificates.
To allow that I added this on the manifest:
```
android:usesCleartextTraffic="true"
android:targetSandboxVersion="1"
android:networkSecurityConfig="@xml/network_security_config"
```
And this is the security config:
```
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="user"/>
</trust-anchors>
</base-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">*</domain>
</domain-config>
</network-security-config>
```
But my app appears on Google Play as not compatible. What can I do? Thank you.
Hey guys, I'm loking for some good firestore implementation example, Im talking like production level, not a demo app where someone shoots some crud queries and forgets to implement snapshots so that the app would be browsable during offline...
I guess what I'm really looking for is some wrapper/manager class for crud + handling caching/network strategy.
Firestore with all its socket sync is not a holy grail apparently. For example on first app launch you still have to preload data unless you want user getting delays on every first time he visits a document.
Bug 1 - In Java classes I can do Alt+Insert and I have this options:
And if I do Alt+Enter I have more options:
Let's go to a Kotlin class, Alt+Insert:
And Alt+Enter in the same Java class:
There are options that are java specific but where is the Tests and Copyright options?
Is this some bug, or were the options removed for kotlin, or did they ever existed?
Bug 2 - Smart completion
In Java Smart completion works nicely to autocomplete interface implementations, just do Ctrl + Shift + Space:
Why does kotlin have no suggestions with Ctrl + Shift + Space?
Bug 3- Auto convert java to kotlin also not working
I try to paste a piece of code from a java file to a kotlin file and it never converts. In the past it even converted code copied from a webpage. The option is enabled in the settings.
I've uninstalled all plugins, uninstalled AS, removed files, reinstalled AS from scratch and the bugs persist.
Anyone has the same issues?
I have been working to get my indie game - SnowFall - to be available in the Audi Application Store for many months. Audi and Volkswagen offer Android based games in the vehicle's head unit via an app store. The apps can only be played while parked. And they have to be formatted to fit their screens.
I was not informed that it is available in the store. But out of curiosity, checked my dashboard today and saw I had 32 installs! Has anyone else published their android apps thru Audi or other vehicle company? Anyone getting better support or communication from the companies?
So I am developing an app that is currently using firebase as a database. I am now looking into a way too store secret keys so they aren't written in the code. I did some research and one of the most secure ways I found was to use a keystore. So I used it following this video: FULL Guide to Encryption & Decryption in Android (Keystore, Ciphers and more) which uses a keystore and writes the hash to a file in the internal storage.
I copied exactly how this video has done it then I went to bed. Then I just started thinking but can't i not just store the secret keys in my firebase database and retrieve it from there? Like is this not a valid approach because I am thinking since the database has limited access, it would be reasonably safe in there. Can anyone tell me which approach is better or if there is a better one i should use?
I have some concerns about the multi-domain modul
What if you need to use usecase class declared in two modules: domain:user and domain:coupon?
To solve #1, we create a usecase interface and declare the interface in the domain:user:api and domain:coupon:api modules, respectively, but how do we share the model declared in the domain?
Thanks for reading
So far we have been using Android modules + git submodules to share our libraries between apps. It is getting tiring and we think we need to publish them as libraries to make version and code management easier.
We are looking to host it ourselves, and want anything that works with android gradle. Is there any FOSS that helps do that? Anybody who have tried or done it and is willing to share experience?
Excited to announce Ksoup 0.2.0, the latest update to our Kotlin Multiplatform library for HTML and XML parsing. This release includes several key fixes and updates specifically for Android, making it easier and more reliable to work with structured data on Android devices.
📲 Android-Specific Improvements:
• Resolved Android Empty Document Error (#96) - Fixes empty document issues that occurred with network libraries
• Metadata Parsing Enhancement - Shortcut icons are now parsed as part of metadata (#89)
• Updated Compile & Target SDKs - Now supporting SDK 35 for both compile and target, with adjustments to ensure compatibility with older Android versions
• Upgraded AGP - Bumped Android Gradle Plugin to 8.7.1 for improved build performance and stability
🔄 General Enhancements:
• WatchOS Support - Expands Ksoup’s cross-platform capabilities (#90)
• JS Unpacking - Added for seamless integration across platforms (#86)
• Upgraded Dependencies: Kotlin 2.0.21, Ktor 3.0.1, Gradle 10.7.2
👉 Check it out and get started here: https://github.com/fleeksoft/ksoup
We’d love to hear your thoughts or feedback from the Android community!
So I learned about the remember keyword to remember some previous code in a composable. That's nice I guess , but I could just run that code in a constructor/remember some states as member variables. Why would I use remember?
I come from C++ btw.
This frustrated me to no end until I resolved it, and Google's own support wasn't any good at helping me. And I couldn't find any info online.
If you have tried to update your "Organization Address" in the Google Payments settings in order that it will display correctly when starting to confirm your Google Play Developer account but you keep seeing your settings revert back to what they were before when you refresh, I have your solution.
Apparently, the problem is that the information does not save correctly following the prompt for "use this address or use suggested address."
So, the way to get around this is to make note of EXACTLY what Google's suggested address for you in (including the +4 of the Zip) and type that in manually as your address.
Ignore Google's attempt at autocomplete when typing in the address, as they will autocomplete to something different than their suggested address (for example, the autocomplete says "Avenue" but the suggested address says "Ave").
If it's a perfect match, then the save will be successful, and you will receive an e-mail saying that the payment settings were updated.
I had thought initially my issue was that I hadn't updated my US tax into, but even once I did that, I was still having my problem of the organization address reverting to my old address upon refresh (and never getting an e-mail that a change happened).
But this "type the exact address that will be suggested in order to avoid the next prompt" method turns out to be the real solution.
Let me know if this post helped you.
I'm about to build an app which will use an llm and was wondering if anyone has been able to use a local llm in production? I'm guessing not but would love to be surprised.
When messing around on my own I use llama right now so hopefully it isnt too much longer!
I'm trying to configure an SD card for my Android Studio virtual device, but the "External File" option is greyed out in the AVD settings. I've already tried changing the target API level from 35 to 33, but the option is still unavailable.
Our team running AS Ladybug has to force quit ADB multiple times a day. We do plug / unplug a lot of USB devices as we have to test on them.
ADB will be running 100% in Activity Monitor and be unresponsive. If you do adb devices it will just sit there until you cmd+c kill it in terminal.
Going into Activity Monitor and force killing it will then get it back in shape as AS will restart it.
This is a newer issue to us but happens to every developer but I don't have replication steps. I know I just get to restarting it multiple times a day, 3 or 4 times.
Currently I'm using github actions ci/cd platform for: lint, ui/unit tests and packaging. I want to add static code analysis - SonarQube wa my first go-to but it turns out they are free only for public repositories. Looking for a good alternative
Hi, I need some help and explanation if u can. Im writing characteristics on my device but doesn't get any response. Did I wrote command in a wrong way? I'm so confused, Log's says im writing but im never reading.
private fun calculateChecksum(data: ByteArray): Byte {
var sum = 0
for (byte in data) {
sum += byte.toInt()
}
return (sum and 0xFF).toByte()
}
val commandGetDataPart1 = byteArrayOf(
0x51.toByte(), // Start byte
0x23.toByte(), // CMD
0x00, 0x00, 0x00, 0x00, // Data
0xA3.toByte() // Stop byte
)
val checksum = calculateChecksum(commandGetDataPart1.copyOfRange(0, 7))
val commandGetDataPart1WithChecksum = commandGetDataPart1 + checksum
Google recommends using play signing which means you won't have access to the keystore file. I want to know if this will cause conflict when you want to release your app to other stores
So I've been kinda force do work with KMP/Compose and wonder - has Android went full compose or material-components (MDC) are still a thing? (having to deal with kotlin-compose combo recently and looking at MDC it looks somewhat nicer…)
Spent whole day trying to get emulator working. Best I managed was running a macOS-13 emulator and took 26min to start the app and run 1 instrumented test.
Where can I find some good script to run instrumented tests on Github Actions CI/CD?
Or should I switch entirely to some other CI/CD?
Posting here cause I think most people who are not that well versed with compose will come across this issue, when working with custom clusters that need to be swtiched around.
I have been trying to create a google maps screen, with clustering, that based on a condition, will switch between markers (positioning and image). When switching, performance is TERRIBLE. It literally lags for 2 seconds, and any click while updating makes it crash.
I can kind of see why this would be terrible for performance, but not how to fix it
@Composable
MyScreen {
GoogleMap(
modifier = Modifier
.weight(weight = 1f),
properties = MapProperties(mapType = MapType.NORMAL),
uiSettings = MapUiSettings(
zoomControlsEnabled = false,
mapToolbarEnabled = false,
tiltGesturesEnabled = false,
myLocationButtonEnabled = false
),
cameraPositionState = cameraPositionState,
onMapLoaded = { }
) {
Clustering(
items = if (selectedType == ProjectType.TYPE_1) items1 else items2,
clusterItemContent = { item ->
val isSelected = (item == selectedItem)
val imageRes = when {
selectedType == ProjectType.TYPE_1 && isSelected -> R.drawable.ic_drawable_1
selectedType == ProjectType.TYPE_1 -> R.drawable.ic_drawable_2
selectedType == ProjectType.TYPE_2 && isSelected -> R.drawable.ic_drawable_3
selectedType == ProjectType.TYPE_2 -> R.drawable.ic_drawable_4
else -> R.drawable.ic_drawable_5
}
Image(
modifier = Modifier.size(36.dp),
painter = painterResource(id = imageRes),
contentDescription = null,
)
},
onClusterItemClick = { item ->
coroutineScope.launch {
...
}
true
}
)
...
}
We have an app that's preloaded into OEM devices. After a FOTA update, certain permissions that were pre-granted (e.g., ACCESS_NOTIFICATION) are reset and un-granted, even though they were granted during the preload process. Question is whether the resetting of ProtectedPermissions is a standard behavior after FOTA updates or if it is an issue related to specific OEMs or device configurations?
Has anyone written or know of a compose written login UI using the Firebase Auth sdk? I've been using the prebuilt UI from Firebase for the past year and its unbelievably broken & annoying.
Seems like a very standardised component, so curious if anyone is aware of an existing version before I go re-invent the wheel!
I use a windows PC and I build my code on archlinux inside virtualbox (windows bad for rust, ndk, env vars etc.). Virtualbox crashes if I try to run the emulator inside it so I've been trying to run the AVD on windows itself. However I can't connect the studio to the emulator. I tried wireless debugging but studio won't discover device. On google I found this but I'm not sure how I do this on windows.
How can I connect the studio to the emulator so that it detects the device and I can run apps? Should I try to first connect using adb?
EDIT: It is 'Host' AVD in title