/r/androiddev

Photograph via snooOG

News for Android developers with the who, what, where, when and how of the Android community.

News / Articles / Talks / Tools / Open source!

About

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:

  • News for Android developers
  • Thoughtful, informative articles
  • Insightful talks and presentations
  • Useful libraries
  • Handy tools
  • Open source applications for studying

Alternatives

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

New Developer Resources

Build your first app

Starting Android career in 2022

Android Job Interview Questions and Answers

App Portfolio Ideas, Tiered List

Awesome Android UI

Material Design Icons

7000 Icons for Jetpack

Weekly Threads Calendar

Autoposted at approx 9AM EST / 2PM GMT

Links

Rules!

Wiki and FAQ!

Discord!

/r/androiddev

242,264 Subscribers

1

Algebraic Data Types In Kotlin

0 Comments
2024/11/03
00:02 UTC

0

How do I solve this error?

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
    }
}
2 Comments
2024/11/02
22:32 UTC

10

allowClearTextTraffic makes app not compatible in Google Play

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.

23 Comments
2024/11/02
11:50 UTC

1

Looking for some good firestore implementation example

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.

3 Comments
2024/11/01
21:23 UTC

51

Ladybug has many bugs or is this just me?

Bug 1 - In Java classes I can do Alt+Insert and I have this options:

https://preview.redd.it/yv1o56f80cyd1.png?width=493&format=png&auto=webp&s=7beaa6d9e54c67a0e816d72042a854263ae76a30

And if I do Alt+Enter I have more options:

https://preview.redd.it/hr88yzeh0cyd1.png?width=673&format=png&auto=webp&s=713d14cc8bcfcdfa1595557da784dc7390bd4796

Let's go to a Kotlin class, Alt+Insert:

https://preview.redd.it/g6g7eld4xbyd1.png?width=409&format=png&auto=webp&s=a9aae88a9ef45955e433002aabc9cb92798593fb

And Alt+Enter in the same Java class:

https://preview.redd.it/6s1idedn0cyd1.png?width=607&format=png&auto=webp&s=21931221cca7f04dc9e43291c746bdfb5325ed19

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:

https://preview.redd.it/xv15cqnk1cyd1.png?width=589&format=png&auto=webp&s=67bafbdd45b26691b154e2ba9f74bdc6e7ffd9df

Why does kotlin have no suggestions with Ctrl + Shift + Space?

https://preview.redd.it/7jkx25u72cyd1.png?width=681&format=png&auto=webp&s=6af06cadd28f1e9672fec6c0933c27f14120df57

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?

29 Comments
2024/11/01
18:35 UTC

5

Has Anyone Developed or Launched for Audi Vehicles?

https://preview.redd.it/7tbv7qzwzbyd1.png?width=914&format=png&auto=webp&s=9df96264a7e63e09a3702d88c65287bc1ee08354

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?

3 Comments
2024/11/01
18:17 UTC

2

Storing Secret keys in firebase or keystore

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?

15 Comments
2024/11/01
09:08 UTC

6

About the Android Multi-Domain Module

I have some concerns about the multi-domain modul

  1. What if you need to use usecase class declared in two modules: domain:user and domain:coupon?

  2. 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

4 Comments
2024/11/01
06:14 UTC

3

Self Hosting Android Library privately

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?

28 Comments
2024/11/01
04:37 UTC

15

[RELEASE] Ksoup 0.2.0 – Enhanced HTML & XML Parsing for Android Developers (Kotlin Multiplatform)

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!

0 Comments
2024/11/01
00:01 UTC

0

Is 'remember' in Jetpack Compose overkill?

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.

11 Comments
2024/10/31
21:08 UTC

7

Tip for anyone frustrated with Google Payments organization address not updating properly when you're trying to do so to get it to match with your DUNS number when verifying your Google Play Developer account.

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.

0 Comments
2024/10/31
19:29 UTC

2

Anybody been able to use a local llm in an app? Is this possible yet?

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!

12 Comments
2024/10/31
16:33 UTC

1

Android Studio: Unable to Set External File as SD Card – Option Greyed Out

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.

https://preview.redd.it/javet9h102yd1.jpg?width=1374&format=pjpg&auto=webp&s=8ef12d083654fc9efd353f1efebb0b43647628ec

2 Comments
2024/10/31
08:37 UTC

15

Force quit ADB multiple times per day on M1 based Mac

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.

17 Comments
2024/10/31
13:38 UTC

0

What static code analyzer do you use in your CI/CD?

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

10 Comments
2024/10/31
12:25 UTC

0

Android Bluetooth GATT (BLE)

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 

https://preview.redd.it/pza2971p23yd1.png?width=752&format=png&auto=webp&s=ef5b82d4cf3bfe77ee2aa6441dc733ec9fbb2d8d

f

8 Comments
2024/10/31
12:20 UTC

1

If you use play signing, can you distribute to other stores?

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

7 Comments
2024/10/31
06:24 UTC

0

Are material-components dead?

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…)

16 Comments
2024/10/30
18:52 UTC

10

Where can I find some decent Github Action CI/CD sample scripts?

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?

3 Comments
2024/10/30
18:07 UTC

6

TypeAlias Show - Immutable and Persistent Lists in Kotlin

0 Comments
2024/10/30
15:00 UTC

11

Performance issue with Jetpack Compose's Google Maps

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
       }
  )
  ...
}
4 Comments
2024/10/30
14:53 UTC

2

ProtectedPermissions Status Reset After FOTA Updates for App Preloads

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?

8 Comments
2024/10/30
10:53 UTC

2

Compose alternative to FirebaseAuthUi

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!

2 Comments
2024/10/30
06:48 UTC

1

Connecting VM Studio with guest AVD

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

2 Comments
2024/10/30
05:42 UTC

Back To Top