I want get the keyboard input in my custom compose?But even i can't show a keybord.
Anyone know how to use it ?help please.
#Composable
fun SmsCodeCompose(onCompleted:(String)->Unit) {
val textInputService = LocalTextInputService.current
Text(text = "12112",
modifier=Modifier.clickable {
Timber.d("input service= $textInputService")
val textInputSession = textInputService?.startInput(
value = TextFieldValue(),
imeOptions = ImeOptions(),
onEditCommand = {it-> it.forEach{
Timber.d("$it")
}
},
onImeActionPerformed = {}
)
textInputSession?.showSoftwareKeyboard()
}.focusable(true))
Related
I'm trying to dynamically swap a text inside a LottieAnimation in jetpack compose.
The lottie file is exported without glyphs
It's working when using the old android view inside a
AndroidView(factory = { context ->
val view = LottieAnimationView(context).apply {
setAnimation(R.raw.testing_no_glyphs)
playAnimation()
repeatCount = LottieConstants.IterateForever
}
val textDel = object : TextDelegate(view) {
override fun getText(layerName: String?, input: String?): String {
return when (layerName) {
"Test234" -> "OtherLettersHere"
else -> super.getText(layerName, input)
}
}
}
val fontDel = object : FontAssetDelegate() {
override fun getFontPath(fontFamily: String?, fontStyle: String?, fontName: String?): String {
return "fonts/[MyFontInside /assets].ttf"
}
}
view.setTextDelegate(textDel)
view.setFontAssetDelegate(fontDel)
return#AndroidView view
})
But I can't find the correct handles in the JetpackCompose version of Lottie to get the same result.
If we export the lottie with glyphs, it's works for the letters in the chars array inside the lottie json. But we want to be able to replace with any letters/symbols, so this isn't a viable solution.
I've noticed in the 5.3.0-SNAPSHOT that a fontMap parameter has been added, but I can't figure out which key to hit with it.
Here is my code:
val dynamicProperties = rememberLottieDynamicProperties(
rememberLottieDynamicProperty(LottieProperty.TEXT, value = "AaBbCcEeFf", keyPath = arrayOf("Test234"))
)
val composition by rememberLottieComposition(
spec = LottieCompositionSpec.RawRes(R.raw.testing)
)
val progress by animateLottieCompositionAsState(composition, iterations = LottieConstants.IterateForever)
LottieAnimation(
composition,
{ progress },
dynamicProperties = dynamicProperties,
fontMap = mapOf("fName" to Typeface.createFromAsset(LocalContext.current.assets, "fonts/[MyFontInside /assets].ttf"))
)
It just shows a blank for all the texts inside the Lottie Animation - so this is kinda where i'm stuck.
After some trial an error I found a way to add the typeface for a specific layer:
val typeface = Typeface.createFromAsset(LocalContext.current.assets, "fonts/[MyFontInside /assets].ttf")
val dynamicProperties = rememberLottieDynamicProperties(
rememberLottieDynamicProperty(LottieProperty.TEXT, value = "AaBbCcEeFf", keyPath = arrayOf("Test234")),
--> rememberLottieDynamicProperty(LottieProperty.TYPEFACE, value = typeface, keyPath = arrayOf("Test234")),
)
Hence there is no need for the fontMap in my case
I followed this document from the developer site.
I want to display the text in an OutlinedTextField from a user input, and have it survive configuration changes.
With the code below, when the user inputs the text from the keyboard, OutlinedTextField doesn't update the text.
HelloContent(name = city.name, onNameChange = { city.name = it})//Doesn't work
However this line of code works properly:
HelloContent(name = temp, onNameChange = { temp = it})//Work
Below is the code which I use to implement:
#Composable
fun HelloScreen() {
var city by rememberSaveable(stateSaver = CitySaver) {
mutableStateOf(City("Hanoi","VietNam"))
}
var temp by rememberSaveable {
mutableStateOf("")
}
Column {
HelloContent(name = city.name, onNameChange = { city.name = it})//Doesn't work
HelloContent(name = temp, onNameChange = { temp = it})//Work
}
}
#Composable
fun HelloContent(name: String, onNameChange: (String) -> Unit) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "Hello, $name",
modifier = Modifier.padding(bottom = 8.dp),
style = MaterialTheme.typography.h5
)
OutlinedTextField(
value = name,
onValueChange = onNameChange,
label = { Text("Name") }
)
}
}
data class City(var name: String, val country: String)
val CitySaver = run {
val nameKey = "Name"
val countryKey = "Country"
mapSaver(save = {mapOf(nameKey to it.name,countryKey to it.country)},
restore = {City(it[nameKey] as String,it[countryKey] as String)})
}
Could you help me to fix the first code block to work?
HelloContent(name = city.name, onNameChange = { city.name = it})//Doesn't work
The TextField will not be updated because you are only modifying city.name not the actual mutableState City object so there is nothing that tells the composer to trigger an update (re-composition).
So modify your first HelloContent composable like this.
HelloContent(name = city.name, onNameChange = { city = city.copy(name = it)})
changing this line
onNameChange = { city.name = it}
to this
onNameChange = { city = city.copy(name = it)}
will make sure your TextField gets updated(re-composed).
Keep in mind, invoking .copy() on data classes guarantees a new instance will be created (provided that you supplied a new value to one of its properties/fields), which is needed by Compose to trigger re-composition.
I have Viewmodel where I keep reference to post. When i want to translate post i run function to update state flow and post and if I put logs there I see that value is changed but my composable did not get any new value. Here is part of code from viewmodel that doing this magic
private var originalText = tile.text?.originalText
private var text = tile.text?.text
private var _postTileState = MutableStateFlow(tile)
val tileState = _postTileState.asStateFlow()
fun translateText() {
val newText = if (_postTileState.value.text?.isTranslated == true)
originalText
else text
val postText = PostText(
text = newText,
isTranslated = !_postTileState.value.text?.isTranslated!!,
originalText = tile.text?.originalText
)
_postTileState.value.text = postText
}
Tile here is one post. And here is parent composable
#Composable
fun PostTileView(
tile: PostTile,
content: #Composable (
tile: PostTile,
) -> Unit
) {
val postTileViewModel = postTileViewModel(
tile,
id = tile.id
)
val postTile = postTileViewModel.tileState.collectAsState().value
Column(
modifier = Modifier
.background(Style.colors.content)
.padding(bottom = 10.dp)
) {
PostTileHeaderView(postTile.author, createdDate = postTile.createdDate)
content(postTile)
if(postTile.text?.originalText != null) {
Button(onClick = {
postTileViewModel.translateText()
}) {
val buttonTitle = if(postTile.text!!.isTranslated == true)
"Show original" else "Translate"
Text(text = buttonTitle)
}
}
PostTags(tags = postTile.tags)
}
}
If i put some other state with this one in viewmodel like some boolean and get reference to it in composable it works fine but with updating post it does not work. What I am missing?
The problem is with your translateText() function, you can't update just a single variable of the object inside the state flow like that _postTileState.value.text = postText, but instead you need to update the hole object so your function should look like that:
fun translateText() {
val newText = if (_postTileState.value.text?.isTranslated == true)
originalText
else text
val postText = PostText(
text = newText,
isTranslated = !_postTileState.value.text?.isTranslated!!,
originalText = tile.text?.originalText
)
_postTileState.value = _postTileState.value.copy(text = postText)
}
Now the state flow will notice that you set a new object.
In the following viewModel code I am generating a list of items from graphQl server
private val _balloonsStatus =
MutableStateFlow<Status<List<BalloonsQuery.Edge>?>>(Status.Loading())
val balloonsStatus get() = _balloonsStatus
private val _endCursor = MutableStateFlow<String?>(null)
val endCursor get() = _endCursor
init {
loadBalloons(null)
}
fun loadBalloons(cursor: String?) {
viewModelScope.launch {
val data = repo.getBalloonsFromServer(cursor)
if (data.errors == null) {
_balloonsStatus.value = Status.Success(data.data?.balloons?.edges)
_endCursor.value = data.data?.balloons?.pageInfo?.endCursor
} else {
_balloonsStatus.value = Status.Error(data.errors!![0].message)
_endCursor.value = null
}
}
}
and in the composable function I am getting this data by following this code:
#Composable
fun BalloonsScreen(
navHostController: NavHostController? = null,
viewModel: SharedBalloonViewModel
) {
val endCursor by viewModel.endCursor.collectAsState()
val balloons by viewModel.balloonsStatus.collectAsState()
AssignmentTheme {
Column(modifier = Modifier.fillMaxSize()) {
when (balloons) {
is Status.Error -> {
Log.i("Reyjohn", balloons.message!!)
}
is Status.Loading -> {
Log.i("Reyjohn", "loading..")
}
is Status.Success -> {
BalloonList(edgeList = balloons.data!!, navHostController = navHostController)
}
}
Spacer(modifier = Modifier.weight(1f))
Button(onClick = { viewModel.loadBalloons(endCursor) }) {
Text(text = "Load More")
}
}
}
}
#Composable
fun BalloonList(
edgeList: List<BalloonsQuery.Edge>,
navHostController: NavHostController? = null,
) {
LazyColumn {
items(items = edgeList) { edge ->
UserRow(edge.node, navHostController)
}
}
}
but the problem is every time I click on Load More button it regenerates the view and displays a new set of list, but I want to append the list at the end of the previous list. As far I understand that the list is regenerated as the flow I am listening to is doing the work behind this, but I am stuck here to get a workaround about how to achieve my target here, a kind hearted help would be much appreciated!
You can create a private list in ViewModel that adds List<BalloonsQuery.Edge>?>
and instead of
_balloonsStatus.value = Status.Success(data.data?.balloons?.edges)
you can do something like
_balloonsStatus.value = Status.Success(myLiast.addAll(
data.data?.balloons?.edges))
should update Compose with the latest data appended to existing one
I am currently trying to write an App for my thesis and currently, I am looking into different approaches. Since I really like Flutter and the Thesis requires me to use Java/Kotlin I would like to use Jetpack compose.
Currently, I am stuck trying to update ListElements.
I want to have a List that shows Experiments and their state/result. Once I hit the Button I want the experiments to run and after they are done update their state. Currently, the run Method does nothing besides setting the state to success.
The problem is I don't know how to trigger a recompose from the viewModel of the ExperimentRow once an experiment updates its state.
ExperimentsActivity:
class ExperimentsActivity : AppCompatActivity() {
val exViewModel by viewModels<ExperimentViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//For now this is just Dummy Data and will be replaced
exViewModel.experiments += listOf(
Experiment("Test1", exViewModel::experimentStateChanged),
Experiment("Strongbox", exViewModel::experimentStateChanged)
)
setContent {
TpmTheme {
// A surface container using the 'background' color from the theme
Surface {
ExperimentScreen(
exViewModel.experiments,
exViewModel::startTests
)
}
}
}
}
}
ExperimentViewModel:
class ExperimentViewModel : ViewModel() {
var experiments by mutableStateOf(listOf<Experiment>())
fun startTests() {
for (exp in experiments) {
exp.run()
}
}
fun experimentStateChanged(experiment: Experiment) {
Log.i("ViewModel", "Changed expState of ${experiment.name} to ${experiment.state}")
// HOW DO I TRIGGER A RECOMPOSE OF THE EXPERIMENTROW FOR THE experiment????
//experiments = experiments.toMutableList().also { it.plus(experiment) }
Log.i("Vi", "Size of Expirments: ${experiments.size}")
}
}
ExperimentScreen:
#Composable
fun ExperimentScreen(
experiments: List<Experiment>,
onStartExperiments: () -> Unit
) {
Column {
LazyColumnFor(
items = experiments,
modifier = Modifier.weight(1f),
contentPadding = PaddingValues(top = 8.dp),
) { ep ->
ExperimentRow(
experiment = ep,
modifier = Modifier.fillParentMaxWidth(),
)
}
Button(
onClick = { onStartExperiments() },
modifier = Modifier.padding(16.dp).fillMaxWidth(),
) {
Text("Run Tests")
}
}
}
#Composable
fun ExperimentRow(experiment: Experiment, modifier: Modifier = Modifier) {
Row(
modifier = modifier
.padding(horizontal = 16.dp, vertical = 8.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(experiment.name)
Icon(
asset = experiment.state.vAsset,
)
}
Experiment:
class Experiment(val name: String, val onStateChanged: (Experiment) -> Unit) {
var state: ExperimentState = ExperimentState.DEFAULT
set(value) {
field = value
onStateChanged(this)
}
fun run() {
state = ExperimentState.SUCCESS;
}
}
enum class ExperimentState(val vAsset: VectorAsset) {
DEFAULT(Icons.Default.Info),
RUNNING(Icons.Default.Refresh),
SUCCESS(Icons.Default.Done),
FAILED(Icons.Default.Warning),
}
There's a few ways to address this but key thing is that you need to add a copy of element (with state changed) to experiments to trigger the recomposition.
One possible example would be
data class Experiment(val name: String, val state: ExperimentState, val onStateChanged: (Experiment) -> Unit) {
fun run() {
onStateChanged(this.copy(state = ExperimentState.SUCCESS))
}
}
and then
fun experimentStateChanged(experiment: Experiment) {
val index = experiments.toMutableList().indexOfFirst { it.name == experiment.name }
experiments = experiments.toMutableList().also {
it[index] = experiment
}
}
though I suspect there's probably cleaner way of doing this.