I want to use a string resource with composable functions, I adjusted my code, but it tells me that I have an unused parameter - name("Zero")
string name="greeting">Hello, my name is $name</string>
//this one works fine but I don't want to use this one --
string name="greeting1">Hello, my name is %1$s</string>
#Composable
fun Greeting(name: String) {
Surface(color = Color.Green, border = BorderStroke(1.dp, color = Color.Cyan)) {
//Text(text = "Hello, my name is $name!", modifier = Modifier.padding(24.dp))
Text(text = stringResource(id = R.string.greeting1,"Zero"))
}
}
#Preview(showBackground = true)
#Composable
fun DefaultPreview() {
GreetingCardTheme {
Greeting("Zero")
}
}
You have
Parameter 'name' is never used
because you have a parameter declared in your composable "name" and you are not using it since you have a fixed string "Zero".
fun Greeting(name: String) {
//...You are not using name
Text(text = stringResource(id = R.string.greeting1,"Zero"))
}
Instead use:
Text(text = stringResource(id = R.string.greeting1,name))
In any case in strings.xml you can also use:
<string name="greeting">Hello, my name is</string>
and in your composable:
Text(text = stringResource(id = R.string.greeting) +" $name")
Related
Hi I don't know how can I call "weight(1f)" in my another comopose funtion
Error gives me
Expression weight cannot be invoke as a function. The funtcion invokle is not found
FoodMenu
#Composable
fun FoodMenu(modifier: Modifier = Modifier)
{
Row (modifier=Modifier.fillMaxWidth()
.wrapContentSize()
)
{
FoodItem()
}
}
FoodItem
#Composable
fun FoodItem(modifier: Modifier = Modifier)
{
Box(modifier = Modifier.weight(1f))
{
Text(text= "Salad")
}
Box(modifier = Modifier.weight(1f))
{
Text(text= "Salad")
}
}
Any help please,.thanks so much
I put Rowscope and it work
#Composable
fun RowScope.FoodItem(modifier: Modifier = Modifier)
{
Box(
modifier = Modifier.weight(1f)) {
Text(text= "Salad")
}
Box(modifier = Modifier.weight(1f))
{
Text(text= "Fries")
}
I have this app which displays a list of "coins" to the users . This list was parsed from an JSON API and I used Jetpack Compose for the UI. I implemented
Here is the code of the Jetpack composable list of "coins"
#Composable
fun CoinListScreen(
navController: NavController,
viewModel: CoinListViewModel = hiltViewModel(),
) {
val state = viewModel.state.value
Surface {
Box(modifier = Modifier.fillMaxSize()) {
Column {
androidx.compose.foundation.Image(painter = painterResource(id = R.drawable.ic_baseline_currency_bitcoin_24),
contentDescription = "BTC",
modifier = Modifier
.fillMaxWidth()
.align(CenterHorizontally)
.size(50.dp, 50.dp)
)
SearchBar(
hint = "Search..",
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
){
viewModel.searchCoinsList(it) **//here I'm calling my search function from the view model, inside my search bar**
}
LazyColumn(modifier = Modifier.fillMaxSize()) {
items(state.coins) { coin ->
Spacer(modifier = Modifier.height(5.dp))
CoinListItem(
coin = coin,
onItemClick = {
navController.navigate(Screen.CoinDetailScreen.route + "/${coin.id}")
}
)
Divider()
}
}
}
if (state.error.isNotBlank()) {
Text(
text = state.error,
color = MaterialTheme.colors.error,
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp)
.align(Alignment.Center)
)
}
if (state.isLoading) {
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
}
}
}
}
**//and this Is my composable search bar**
#Composable
fun SearchBar(
modifier: Modifier = Modifier,
hint: String = "",
onSearch: (String) -> Unit = {}
) {
var text by remember {
mutableStateOf("")
}
var isHint by remember {
mutableStateOf(hint != "")
}
Box(modifier = modifier){
BasicTextField(
value = text,
onValueChange = {
text = it
onSearch(it)
},
maxLines = 1,
singleLine = true,
modifier = Modifier
.fillMaxWidth()
.shadow(5.dp, CircleShape)
.background(Color.White, CircleShape)
.padding(horizontal = 20.dp, vertical = 12.dp)
.onFocusChanged {
isHint = it.isFocused != true
}
)
if(isHint){
Text(
text = hint,
color = Color.LightGray,
modifier = Modifier.padding(horizontal = 20.dp, vertical = 12.dp)
)
}
}
}
and this is my view model, this is where I'm implementing the search function, this is where I'm lost, variables that I'm searching for are name, rank, and symbol from the Coin domain list
#HiltViewModel //injecting the use case
class CoinListViewModel #Inject constructor (
private val getCoinsUseCase: GetCoinsUseCase,
) : ViewModel() {
//vmstate Live Template, only the view model touches it
private val _state =
mutableStateOf(CoinListState())
val state: State<CoinListState> = _state
**//for search purposes , this is where I'm lost**
private var coinsList = mutableStateOf<List<Coin>>(listOf())
private var cachedCoinsList = listOf<Coin>()
private var isSearchStarting = true
private var isSearching = mutableStateOf(false)
init {
getCoins()
}
**//for search purposes , this is where I'm lost**
fun searchCoinsList(query: String){
val listToSearch = if(isSearchStarting){
coinsList.value
} else {
cachedCoinsList
}
viewModelScope.launch(Dispatchers.Default) {
if(query.isEmpty()){
coinsList.value = cachedCoinsList
isSearching.value = false
isSearchStarting = true
return#launch
}
val results = listToSearch.filter {
//val iterate: Int = coins.size
it.name.contains(query.trim(), ignoreCase = true) ||
(it.rank.toString() == query.trim()) ||
it.symbol.contains(query.trim(), ignoreCase = true)
}
if(isSearchStarting){
cachedCoinsList = coinsList.value
isSearchStarting = false
}
coinsList.value = results
isSearching.value = true
}
}
//function that calls our GetCoinsUseCase and puts the data inside the state object
//to display that in the UI
private fun getCoins() {
//overwrote the invoke function earlier for the use case which allows us to call the use case as a function
getCoinsUseCase().onEach { result ->
when (result) {
is Resource.SUCCESS -> {
_state.value =
CoinListState(coins = result.data ?: arrayListOf())
}
is Resource.ERROR -> {
_state.value =
CoinListState(
error = result.message ?: "An unexpected error occurred"
)
}
is Resource.LOADING -> {
_state.value = CoinListState(isLoading = true)
}
}
}.launchIn(viewModelScope)
}
}
CoinsListState data class used in view model
data class CoinListState(
val isLoading: Boolean = false,
val coins: ArrayList<Coin> = arrayListOf(),
val error: String = ""
)
this is my "GetCoinsUseCase" to get the coins
class GetCoinsUseCase #Inject constructor(
private val repository: CoinRepository
) {
// overwriting the operator fun invoke allows us to call the use case
//GetCoinsUseCase as if it was a function, and we return a flow because
// we want to emit states LOADING -> for progress bar, SUCCESS -> attach list of coins,
// and ERROR
operator fun invoke(): kotlinx.coroutines.flow.Flow<Resource<ArrayList<Coin>>> = flow {
try {
emit(Resource.LOADING<ArrayList<Coin>>())
//we mapped it to toCoin because we returning a list of coin, not coinDTO
val coins = repository.getCoins().map { it.toCoin() }
emit(Resource.SUCCESS<ArrayList<Coin>>(coins as ArrayList<Coin>))
}catch (e: HttpException){
emit(Resource.ERROR<ArrayList<Coin>>(e.localizedMessage ?: "An unexpected error occurred"))
}catch (e: IOException){
emit(Resource.ERROR<ArrayList<Coin>>("Couldn't reach server. Check connection"))
}
}
}
just the coin repository that is implemented in another place
interface CoinRepository {
//repository definitions
suspend fun getCoins() : ArrayList<CoinDTO>
suspend fun getCoinById(coinId: String) : CoinDetailDTO
}
This is my domain - Domain - only contains the data needed
data class Coin(
var id: String,
var isActive: Boolean,
var name: String,
var rank: Int,
var symbol: String
)
and this is how I'm mapping it
data class CoinDTO(
val id: String,
#SerializedName("is_active")
val isActive: Boolean,
#SerializedName("is_new")
val isNew: Boolean,
val name: String,
val rank: Int,
val symbol: String,
val type: String
)
fun CoinDTO.toCoin(): Coin {
return Coin(
id = id,
isActive = isActive,
name = name,
rank = rank,
symbol = symbol,
// logo = CoinDetailLogo(logo = String()).logo
)
}
Coin list item if needed for reference, this is what is displayed to the user in the list
#Composable
fun CoinListItem (
coin: Coin,
onItemClick: (Coin) -> Unit
) {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable { onItemClick(coin) }
.padding(20.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = "${coin.rank}. ${coin.name} (${coin.symbol})",
style = MaterialTheme.typography.body1,
overflow = TextOverflow.Ellipsis
)
Text(
text = if(coin.isActive) "active" else "inactive",
color = if(coin.isActive) Color.Green else Color.Red,
fontStyle = FontStyle.Italic,
textAlign = TextAlign.End,
style = MaterialTheme.typography.body2,
modifier = Modifier.align(CenterVertically)
)
}
}
as well as the "Resource" generic for states
//UIStates
sealed class Resource<T>(val data: T? = null, val message: String? = null) {
class SUCCESS<T>(data: T) : Resource<T>(data)
class ERROR<T>(message: String, data: T? = null) : Resource<T>(data, message)
class LOADING<T>(data: T? = null) : Resource<T>(data)
}
again, given this info, how can I get the function searchCoinList in the view model to correctly view the searched data (name, rank, or symbol) when it is called in the CoinListScreen inside the Search Bar. Thank you so much
It seems like you want to implement a basic instant search functionality. It's pretty easy to achieve using Kotlin's StateFlow and its operators. Consider the following implementation with description:
// CoinListViewModel
private val queryFlow = MutableStateFlow("")
private val coinsList = mutableStateOf<List<Coin>>(listOf())
init {
queryFlow
.debounce(300) // filters out values that are followed by the newer values within the given timeout. The latest value is always emitted.
.filterNot { query -> userInput.isEmpty() } // filter the unwanted string like an empty string in this case to avoid the unnecessary network call.
.distinctUntilChanged() // to avoid duplicate network calls
.flowOn(Dispatchers.IO) // Changes the context where this flow is executed to Dispatchers.IO
.flatMapLatest { query -> // to avoid the network call results which are not needed more for displaying to the user
getCoinsUseCase(query).catch { emitAll(flowOf(emptyList())}
}
.onEach { coins: List<Coin> -> // go through each list of Coins
coinsList.value = coins
}
.launchIn(viewModelScope)
}
fun searchCoinsList(query: String) {
queryFlow.value = query
}
I'm using Glide in an Android, Kotlin, Jetpack project. When the photos are loading, the DEFAULT_MOVIE_IMAGE appears. However if one of the movies that is returned has a null value for an image URL, no image appears and the card shrinks down to the size of only the title. I'm trying to set it so the DEFAULT_MOVIE_IMAGE will appear if there is a null value for the movie poster url.
Setting a placeholder or an error image does not appear to be working.
Image part of the MovieCard composable
movie.posterPath?.let { url ->
val image = loadPicture(url = url, defaultImage = DEFAULT_MOVIE_IMAGE).value
image?.let { img ->
Image(
bitmap = img.asImageBitmap(),
contentDescription = "Movie Projector",
modifier = Modifier
.fillMaxWidth()
.height(450.dp),
contentScale = ContentScale.Fit
)
}
ImageUtils.kt
const val DEFAULT_MOVIE_IMAGE = R.drawable.movie_placeholder
const val POSTER_BASE_URL = "https://image.tmdb.org/t/p/w500"
#Composable
fun loadPicture(
url: String,
#DrawableRes defaultImage: Int
): MutableState<Bitmap?> {
val bitmapState: MutableState<Bitmap?> = remember { mutableStateOf(null) }
Glide.with(LocalContext.current)
.asBitmap()
.load(defaultImage)
.into(object : CustomTarget<Bitmap>() {
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
bitmapState.value = resource
}
override fun onLoadCleared(placeholder: Drawable?) {
}
})
Glide.with(LocalContext.current)
.asBitmap()
.load("https://image.tmdb.org/t/p/w500$url")
.error(R.drawable.space_dog_laika1)
.into(object : CustomTarget<Bitmap>() {
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
bitmapState.value = resource
}
override fun onLoadCleared(placeholder: Drawable?) {
}
})
return bitmapState
}
I ended up wrapping the image piece of the movie card in an if/else statement. I'm not sure if this is best practice or the most terse solution, but it worked.
if (movie.posterPath != null) {
movie.posterPath?.let { url ->
val image = loadPicture(url = url, defaultImage = DEFAULT_MOVIE_IMAGE).value
image?.let { img ->
Image(
bitmap = img.asImageBitmap(),
contentDescription = "Movie Poster",
modifier = Modifier
.fillMaxWidth()
.height(450.dp),
contentScale = ContentScale.Fit
)
}
}
} else {
val image: Painter = painterResource(id = DEFAULT_MOVIE_IMAGE)
Image(
painter = image,
contentDescription = "Film Projector",
modifier = Modifier
.fillMaxWidth()
.height(450.dp),
contentScale = ContentScale.Fit
)
}
Looking for code that will do conversions like this:
"MyCamelCaseA" to "my_camel_case_a"
"AMultiWordString" to "a_multi_word_string"
"my_camel_case_a" to "myCamelCaseA" or "MyCamelCaseA"
"a_multi_word_string" to "aMultiWordString" or "AMultiWordString"
Here are extensions to the String class that use regex and replacements to convert a string from camel case to snake case, and from snake case to camel case:
val camelRegex = "(?<=[a-zA-Z])[A-Z]".toRegex()
val snakeRegex = "_[a-zA-Z]".toRegex()
// String extensions
fun String.camelToSnakeCase(): String {
return camelRegex.replace(this) {
"_${it.value}"
}.toLowerCase()
}
fun String.snakeToLowerCamelCase(): String {
return snakeRegex.replace(this) {
it.value.replace("_","")
.toUpperCase()
}
}
fun String.snakeToUpperCamelCase(): String {
return this.snakeToLowerCamelCase().capitalize()
}
Here are examples using the String extension:
print("${"MyCamelCaseA".camelToSnakeCase()}\n")
my_camel_case_a
print("${"AMultiWordString".camelToSnakeCase()}\n")
a_multi_word_string
"my_camel_case_a".snakeToLowerCamelCase()
myCamelCaseA
"my_camel_case_a".snakeToUpperCamelCase()
MyCamelCaseA
Here's my stab at this.
fun String.camelToSnakeCase() = fold(StringBuilder(length)) { acc, c ->
if (c in 'A'..'Z') (if (acc.isNotEmpty()) acc.append('_') else acc).append(c + ('a' - 'A'))
else acc.append(c)
}.toString()
My approach is also written in the form of extension function, but it does not use regular expressions, instead going character-by-character, processing them and folding the processing result into the accumulator, which at the beginning is an empty StringBuilder. The processing is as follows:
if the character is not an upper-case Latin letter, add it to accumulator as is
if the character is an upper-case Latin letter, then also check if this is not the first character of the string (accumulator is not empty). If it is not, then add underscore to accumulator. Finally add lower-cased character.
One thing to note, is that kotlin.text.StringBuilder is used, not the JDK one.
I would go with these implementations:
fun String.toCamelCase() =
split('_').joinToString("", transform = String::capitalize)
... which splits the string using snakes as delimiters, and then reattaches the parts as capitalized words without a delimiter.
fun String.toSnakeCase() = replace(humps, "_").toLowerCase()
private val humps = "(?<=.)(?=\\p{Upper})".toRegex()
... which uses a regex to find the positions before humps, inserting snakes, and then converts the whole string to lowercase. The regex consists of two parts, the first one (?<=.) is a positive look-behind saying that it must be preceded by a character, and the second part (?=\\p{Upper}) is using a positive look-ahead saying it must be followed by an uppercase character.
If you have jackson-databind in your classpath, you can use the following utility function:
import com.fasterxml.jackson.databind.PropertyNamingStrategies
fun String.toSnakeCase(): String =
PropertyNamingStrategies.SnakeCaseStrategy().translate(this)
fun main() {
// should output this_is_the_case
println("thisIsTheCase".toSnakeCase())
}
this is my try with kotlin only
val camelCaseString = "thisIsCamelCase"
val snakeCaseString = camelCaseString.map {
if (it.isUpperCase()){
"_${it.toLowerCase()}"
}else
{"$it"}
}
.joinToString(separator = "")
System.out.println("here is your snake string: $snake_case_string")
here is your snake string: this_is_camel_case
convert from snake to camel
val snakeCaseString = "snake_case_string"
val camelCase = StringBuilder()
var prevChar = '$'
snakeCaseString.forEach {
if(prevChar.equals('_')){
camelCase.append(it.toUpperCase())
}else if(!it.equals('_')){
camelCase.append(it)
}
prevChar = it
}
System.out.println(camelCase.toString())
snakeCaseString
I took one of the answers here, added Title Case and changed the API a bit
val camelRegex = "(?<=[a-zA-Z])[A-Z]".toRegex()
val snakeRegex = "_[a-zA-Z]".toRegex()
#JvmInline
value class SnakeCaseString(private val string: String) {
fun toCamelCase(): String = snakeRegex.replace(string) { it.value.replace("_", "").uppercase() }
fun toUpperCamelCase(): String =
toCamelCase().replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
fun toTitleCase(): String = snakeRegex.replace(string) { it.value.replace("_", " ").uppercase() }
.replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
}
#JvmInline
value class CamelCaseString(private val string: String) {
fun toSnakeCase(): String = camelRegex.replace(string) { "_${it.value}" }.lowercase()
fun toTitleCase(): String = camelRegex.replace(string) { "_${it.value}" }
.replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
}
fun String.asSnakeCase() = SnakeCaseString(this)
fun String.asCamelCase() = CamelCaseString(this)
If you want a method with an input and output, this is how I did it:
private fun convertCamelToSnakeCase(camelCase : String) : String {
val snakeCase = StringBuilder()
for(character in camelCase) {
if(character.isUpperCase()) {
snakeCase.append("_${character.toLowerCase()}")
} else {
snakeCase.append(character)
}
}
return snakeCase.removePrefix("_").toString()
}
I would like to store frequently used strings inside an object. On top of this, I would also like to conveniently add a functionality similar to the mkString function's, where if there are two objects of a certain type, they can be appended with a certain character or set of characters.
Here is what I have until now:
import org.scalatest._
class MyKey(val inputValue: String) {
val value = inputValue
def + (otherMyKeys: MyKey): MyKey = {
new MyKey(this.value + "." + otherMyKeys.value)
}
override def toString(): String = this.value.toString
}
object MyKeys {
val SPARK = new MyKey("spark")
val JSON = new MyKey("json")
val TITLE = new MyKey("title")
val URI = new MyKey("uri")
}
class MyKeySpec extends FlatSpec with Matchers {
"MyKey" should "not put a fullstop character when there is only one value" in {
MyKeys.SPARK should not equal("spark")
MyKeys.SPARK.toString() should equal("spark")
}
it should "put a fullstop character between multiple keys" in {
val actual = MyKeys.SPARK + MyKeys.JSON + MyKeys.TITLE + MyKeys.URI
val expected = "spark.json.title.uri"
actual should not equal(expected)
actual.toString() should equal(expected)
}
it should "work even when the same key is repeated multiple times" in {
val actual = MyKeys.SPARK + MyKeys.SPARK + MyKeys.SPARK
val expected = "spark.spark.spark"
actual should not equal(expected)
actual.toString() should equal(expected)
}
}
This solution is elegant in using the "+" function but does not work without a toString. Is there a more elegant solution where I would not even have to use the toString function? The aim is to make a string like "spark.json.spark.spark" as MyKeys.SPARK + MyKey.JSON + MyKey.SPARK + MyKey.SPARK
I can use the mkString function to do the same thing but that, I feel, is not super intuitive. How do I achieve that?
One thing you might do is define an apply() method instead of overriding the toString().
def apply(): String = this.value
Then you get...
MyKeys.SPARK should not equal("spark")
MyKeys.SPARK() should equal("spark")
... etc.
The problem with your design is that anything of type MyKey is, obviously, not a String. It's almost a String, but still needs some catalyst to make the transition, something like .toString() or, as I suggested, the hidden .apply() method.
You could make MyKeys.SPARK a real String, but the + method for String already has a meaning and can't be redefined. If you were OK using a different operator then it could be done.
object MyKeys {
val SPARK = "spark"
val JSON = "json"
val TITLE = "title"
val URI = "uri"
}
implicit class Dotter(str :String) {
def *(append :String) = str + "." + append
}
MyKeys.SPARK * MyKeys.JSON * MyKeys.SPARK //res0: String = spark.json.spark
Have you considered using implicit conversions for this?
class MyKey(val value: String) {
def + (otherMyKeys: MyKey): MyKey = {
new MyKey(this.value + "." + otherMyKeys.value)
}
}
object MyKey {
implicit def myKeyToString(myKey: MyKey): String = myKey.value
}
object MyKeys {
val SPARK = new MyKey("spark")
val JSON = new MyKey("json")
}
val result: String = MyKeys.SPARK + MyKeys.JSON // spark.json