How to load images from the hard disk when using Kotlin compose on the desktop?
other answers are outdated, as per Compose 1.0.0-beta5 you should do the following:
Image(painterResource("image.jpg"))
if you want to load a bitmap only
val bitmap = useResource("image.jpg") { loadImageBitmap(it) }
Only the file name is required (not full path), but make sure that your resources are in src/main/resources/image.jpg
You can get ImageAsset with this function
fun imageFromFile(file: File): ImageAsset {
return org.jetbrains.skia.Image.makeFromEncoded(file.readBytes()).asImageAsset()
}
Full example:
import androidx.compose.desktop.Window
import androidx.compose.foundation.Image
import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.ImageAsset
import androidx.compose.ui.graphics.asImageAsset
import java.io.File
fun main() = Window {
val file = File("D:\\images\\my_image.PNG")
val image = remember { imageFromFile(file) }
Image(asset = image)
}
fun imageFromFile(file: File): ImageAsset {
return org.jetbrains.skia.Image.makeFromEncoded(file.readBytes()).asImageAsset()
}
This worked for me.
Image(bitmap = imageFromResource("image.png"),
"image",
)
contentDescription is necessary, but can be anything you'd like. You can also add modifiers such as
val imageModifier = Modifier
.height(240.dp)
.fillMaxWidth()
.clip(RoundedCornerShape(12.dp))
Image(bitmap = imageFromResource("header.png"),
"image",
imageModifier,
contentScale = ContentScale.Fit
)
Try this one:
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.res.loadImageBitmap
import java.io.File
val file = File(path)
val imageBitmap: ImageBitmap = remember(file) {
loadImageBitmap(file.inputStream())
}
Image(
painter = BitmapPainter(image = imageBitmap),
contentDescription = null
)
Image.asImageBitmap() is deprecated. Use new Image.toComposeImageBitmap(). For now (01.04.2022) it's not deprecated yet:
#Composable
fun CanvasArea2() {
val image = remember { imageFromFile(File("C:/Users/Admin/Desktop/banana.png")) }
Canvas(modifier = Modifier.background(color = Color(0xFFFFFFFF))) {
drawImage(image)
}
}
fun imageFromFile(file: File): ImageBitmap {
return org.jetbrains.skia.Image.makeFromEncoded(file.readBytes()).toComposeImageBitmap()
}
from the docs : Loading images from device storage or network asynchronously
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.loadImageBitmap
import androidx.compose.ui.res.loadSvgPainter
import androidx.compose.ui.res.loadXmlImageVector
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.singleWindowApplication
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.xml.sax.InputSource
import java.io.File
import java.io.IOException
import java.net.URL
fun main() = singleWindowApplication {
val density = LocalDensity.current
Column {
AsyncImage(
load = { loadImageBitmap(File("sample.png")) },
painterFor = { remember { BitmapPainter(it) } },
contentDescription = "Sample",
modifier = Modifier.width(200.dp)
)
AsyncImage(
load = { loadSvgPainter("https://github.com/JetBrains/compose-jb/raw/master/artwork/idea-logo.svg", density) },
painterFor = { it },
contentDescription = "Idea logo",
contentScale = ContentScale.FillWidth,
modifier = Modifier.width(200.dp)
)
AsyncImage(
load = { loadXmlImageVector(File("compose-logo.xml"), density) },
painterFor = { rememberVectorPainter(it) },
contentDescription = "Compose logo",
contentScale = ContentScale.FillWidth,
modifier = Modifier.width(200.dp)
)
}
}
#Composable
fun <T> AsyncImage(
load: suspend () -> T,
painterFor: #Composable (T) -> Painter,
contentDescription: String,
modifier: Modifier = Modifier,
contentScale: ContentScale = ContentScale.Fit,
) {
val image: T? by produceState<T?>(null) {
value = withContext(Dispatchers.IO) {
try {
load()
} catch (e: IOException) {
// instead of printing to console, you can also write this to log,
// or show some error placeholder
e.printStackTrace()
null
}
}
}
if (image != null) {
Image(
painter = painterFor(image!!),
contentDescription = contentDescription,
contentScale = contentScale,
modifier = modifier
)
}
}
/* Loading from file with java.io API */
fun loadImageBitmap(file: File): ImageBitmap =
file.inputStream().buffered().use(::loadImageBitmap)
fun loadSvgPainter(file: File, density: Density): Painter =
file.inputStream().buffered().use { loadSvgPainter(it, density) }
fun loadXmlImageVector(file: File, density: Density): ImageVector =
file.inputStream().buffered().use { loadXmlImageVector(InputSource(it), density) }
/* Loading from network with java.net API */
fun loadImageBitmap(url: String): ImageBitmap =
URL(url).openStream().buffered().use(::loadImageBitmap)
fun loadSvgPainter(url: String, density: Density): Painter =
URL(url).openStream().buffered().use { loadSvgPainter(it, density) }
fun loadXmlImageVector(url: String, density: Density): ImageVector =
URL(url).openStream().buffered().use { loadXmlImageVector(InputSource(it), density) }
/* Loading from network with Ktor client API (https://ktor.io/docs/client.html). */
/*
suspend fun loadImageBitmap(url: String): ImageBitmap =
urlStream(url).use(::loadImageBitmap)
suspend fun loadSvgPainter(url: String, density: Density): Painter =
urlStream(url).use { loadSvgPainter(it, density) }
suspend fun loadXmlImageVector(url: String, density: Density): ImageVector =
urlStream(url).use { loadXmlImageVector(InputSource(it), density) }
#OptIn(KtorExperimentalAPI::class)
private suspend fun urlStream(url: String) = HttpClient(CIO).use {
ByteArrayInputStream(it.get(url))
}
*/
Using compose-1.1.0, this works for me.
import org.jetbrains.skia.Image
// ...
Image(
contentDescription = "image",
bitmap = Image.Companion.makeFromEncoded(imageBytes).toComposeImageBitmap()
)
Related
I know how to use the email password part of firebase. but I'm having a problem with finding any docs or code for the Login/Signup with google/facebook in jetpack compose.
Try this out
package com.example.signinwithgoogle
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Email
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.example.signinwithgoogle.ui.theme.SignInWithGoogleTheme
import com.firebase.ui.auth.R
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
import com.google.android.gms.auth.api.signin.GoogleSignInClient
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
import com.google.android.gms.tasks.Task
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.GoogleAuthProvider
private const val TAG = "MainActivity"
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
SignInWithGoogleTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
GoogleSignIn()
}
}
}
}
}
#SuppressLint("CoroutineCreationDuringComposition")
#Composable
fun GoogleSignIn(){
Log.d(TAG, "GoogleSignIn: called")
val context = LocalContext.current
val mAuth = FirebaseAuth.getInstance()
val result1 = remember {
mutableStateOf<ActivityResult?>(null)
}
val startForResult =
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
result1.value = result
}
Surface(modifier = Modifier.fillMaxSize()) {
if (mAuth.currentUser != null) {
Log.d(TAG, "FirstPage: going to second page")
Toast.makeText(context, "Already signed in", Toast.LENGTH_SHORT).show()
} else {
val googleSignInClient = getGoogleLoginAuthI(
context,
stringResource(id = R.string.default_web_client_id)
)
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Button(onClick = {
startForResult.launch(googleSignInClient.signInIntent)
}
) {
Image(
imageVector = Icons.Default.Email,
contentDescription = null,
modifier = Modifier
.size(48.dp)
.padding(4.dp),
contentScale = ContentScale.FillBounds
)
Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing))
Text(text = "Sign With Google")
}
if (result1.value != null) {
val result = result1.value
if (result != null) {
if (result.resultCode == Activity.RESULT_OK) {
val intent = result.data
if (result.data != null) {
val task: Task<GoogleSignInAccount> =
com.google.android.gms.auth.api.signin.GoogleSignIn.getSignedInAccountFromIntent(intent)
handleGoogleSignInResult(task, context = context)
}
}
}
}
}
}
}
}
fun handleGoogleSignInResult(task: Task<GoogleSignInAccount>, context: Context): List<String> {
Log.d(TAG, "handleSignInResult: called")
val user = task.result
firebaseAuthWithGoogleI(task, context = context)
return listOf(
user.id.toString(),
user.email.toString(),
user.displayName.toString(),
user.familyName.toString(),
user.givenName.toString(),
user.photoUrl.toString(),
user.serverAuthCode.toString()
)
}
fun firebaseAuthWithGoogleI(task: Task<GoogleSignInAccount>, context: Context) {
Log.d(TAG, "firebaseAuthWithGoogle: login with firebase")
val credential = GoogleAuthProvider.getCredential(task.result.idToken, null)
FirebaseAuth.getInstance().signInWithCredential(credential).addOnCompleteListener { t ->
if (t.isSuccessful) {
showToastMsg(context = context, "Auth successful")
Log.d(TAG, "firebaseAuthWithGoogle: Added to auth")
} else {
showToastMsg(context = context, "Auth failed")
Log.d(TAG, "firebaseAuthWithGoogle: failed ${t.exception}")
}
}.addOnFailureListener {
showToastMsg(context = context, "Auth failed")
Log.d(TAG, "firebaseAuthWithGoogle: failed ${it.message}")
}
}
fun getGoogleLoginAuthI(context: Context, requestIdToken: String): GoogleSignInClient {
Log.d(TAG, "getGoogleLoginAuth: called")
val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestEmail()
.requestIdToken(requestIdToken)
.requestId()
.requestProfile()
.build()
return com.google.android.gms.auth.api.signin.GoogleSignIn.getClient(context, gso)
}
fun showToastMsg(context: Context, msg: String) {
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
}
I got some issuse by trying to hoist a MutableList.
The List is initializied in the MainActivity
package com.example.jetnoteapp
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import com.example.jetnoteapp.ui.theme.JetNoteAppTheme
import com.example.jetnoteapp.view.JetNoteUi
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val noteList = remember { mutableStateListOf<NoteItem>() }
noteList.add(NoteItem("Relaxing", "the whole day"))
noteList.add(NoteItem("Take photos", "Sony or Canon?"))
JetNoteAppTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
JetNoteUi(
noteList = noteList,
addToNoteList = { newNote -> noteList.add(newNote) },
removeFromNoteList = { noteIndex -> noteList.removeAt(noteIndex) }
)
}
}
}
}
}
The "NoteItem" is declared in a dataclass like so:
package com.example.jetnoteapp
data class NoteItem(var title: String, var description: String)
Now i can hoist the declarde List to the Composable function, but with the given lambdas I got an error in the JetNotUi composable.
Type mismatch: inferred type is com.example.jetnoteapp.view.JetNoteUi.NoteItem but com.example.jetnoteapp.NoteItem was expected
package com.example.jetnoteapp.view
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.jetnoteapp.NoteItem
import com.example.jetnoteapp.showJetNoteToast
#Preview
#Composable
fun JetNoteUi(
noteList: List<NoteItem> = listOf(),
addToNoteList: (NoteItem) -> Unit = {},
removeFromNoteList: (Int) -> Unit = {}
) {
data class NoteItem(var title: String, var description: String)
var title by remember {mutableStateOf("")}
var description by remember { mutableStateOf("")}
val context = LocalContext.current
Scaffold(
modifier=Modifier.fillMaxWidth(),
topBar={
TopAppBar(backgroundColor = Color.Blue) {
Text(text="JetNoteApp", fontSize=14.sp, color=Color.White)
}
}
) {
Column(modifier = Modifier.padding(13.dp)) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
JetNoteInputField(
label = "title",
value = title,
onValueChange = { title = it }
)
JetNoteInputField(
label = "description",
value = description,
onValueChange = { description = it }
)
Button(
modifier = Modifier.padding(bottom = 8.dp),
onClick = {
if (title.isNotEmpty() && description.isNotEmpty()) {
addToNoteList(NoteItem(title, description))
title = ""
description = ""
}
else {
if (title.isEmpty()) {
showJetNoteToast(context, message = "title can't be empty.")
}
if (description.isEmpty()) {
showJetNoteToast(context, message = "description can't be empty.")
}
}
},
content = { Text("save") }
)
}
Text(text = "notes:")
Divider(
modifier = Modifier
.height(3.dp)
.padding(top = 2.dp), color = Color.Black
)
Column {
noteList.forEach {
JetNoteListItem(
title = it.title,
description = it.description
)
}
}
}
}
}
I got the following Error and don't now why. It is the same data class, so why appears this Error!?
Type mismatch: inferred type is com.example.jetnoteapp.view.JetNoteUi.NoteItem but com.example.jetnoteapp.NoteItem was expected
In your activity, you have:
val noteList = remember { mutableStateListOf<NoteItem>() }
And the package of MainActivity is package com.example.jetnoteapp.
Looking at the imports in your MainActivity, there is no import for NoteItem, so there must be a class NoteItem in the same package, i.e.: com.example.jetnoteapp.NoteItem.
But in your JetNoteUi, there is another definition of NoteItem.
Like below:
data class NoteItem(var title: String, var description: String)
And you use it with this line:
addToNoteList(NoteItem(title, description))
This causes the class mismatched.
Remove this line
data class NoteItem(var title: String, var description: String)
from your function, try it again.
BTW, removing it means you will use the same class, which is in the package: com.example.jetnoteapp.NoteItem
So i have made a calendar. I am now trying to make two arrow buttons jump through the months. The only problem is that everytime i click the Iconbutton the ++ doesnt do anything the first time but does something on the second... why is this can someone please help
package com.jens.svensson.jenson_calendar
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material.icons.filled.ArrowForward
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.*
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.hilt.navigation.compose.hiltViewModel
import com.jens.svensson.jenson_calendar.data.model.CalendarColors
import com.jens.svensson.jenson_calendar.ui.CalendarViewModel
import com.jens.svensson.jenson_calendar.ui.events.CalendarEvent
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
#Composable
fun Calendar(
calendarViewModel: CalendarViewModel = hiltViewModel(),
isRoundedCorners: Boolean = false,
startMonth: Int,
calendarColor: CalendarColors,
textStyle: TextStyle,
onDissmissDialog: () -> Unit,
size: Configuration
){
val calendarYears = calendarViewModel.calendarYears
val showMenuState = calendarViewModel.dropDownMenuState.value.showDropDown
var dropDownPickedState = calendarViewModel.dropDownMenuState.value.pickedItem
val sizeHeight = size.screenHeightDp
val sizeWidth = size.screenWidthDp
val width = sizeWidth * 0.95
val height = sizeHeight * 0.95
val coroutineScope = rememberCoroutineScope()
Dialog(onDismissRequest = onDissmissDialog, properties = DialogProperties(dismissOnClickOutside = true)) {
Column(modifier = Modifier
.size(width = width.dp, height = height.dp)
.padding(15.dp)
.background(
calendarColor.calendarColor,
shape = if (isRoundedCorners) RoundedCornerShape(10) else RectangleShape
)) {
Header(isRoundedCorners = isRoundedCorners, color = calendarColor.headerColor)
Row(
modifier = Modifier
.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween
) {
Box(
modifier = Modifier
.align(Alignment.CenterVertically)
.padding(start = 22.dp)
) {
Row(Modifier.clickable { calendarViewModel.onEvent(CalendarEvent.ShowDropDown(!showMenuState)) }) {
Text(
text = calendarViewModel.standardMonths[calendarViewModel.dropDownMenuState.value.pickedItem],
style = MaterialTheme.typography.h6,
color = calendarColor.mainCalendarTextColor
)
Icon(
imageVector = Icons.Default.ArrowDropDown,
contentDescription = "Month dropdown arrow",
tint = calendarColor.mainCalendarTextColor
)
}
DropdownMenu(
expanded = showMenuState,
onDismissRequest = { calendarViewModel.onEvent(CalendarEvent.ShowDropDown(!showMenuState)) }) {
calendarViewModel.standardMonths.forEachIndexed { index, month ->
DropdownMenuItem(onClick = { calendarViewModel.onEvent(CalendarEvent.ClickedMenuItem(index))
}) {
Row() {
Text(text = month, style = MaterialTheme.typography.h6)
}
}
}
}
}
IconButton(modifier = Modifier.align(Alignment.CenterVertically), onClick = {}) {
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = "Go back one month arrow",
tint = calendarColor.mainCalendarTextColor
)
}
IconButton(modifier = Modifier.align(Alignment.CenterVertically), onClick = {
calendarViewModel.onEvent(CalendarEvent.ClickedMenuItem(dropDownPickedState++))
}) {
Icon(
imageVector = Icons.Default.ArrowForward,
contentDescription = "Go forward one month arrow",
tint = calendarColor.mainCalendarTextColor
)
}
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
calendarViewModel.datesList.forEach {
Text(text = it, color = calendarColor.mainCalendarTextColor)
}
}
LazyRow(state = calendarViewModel.listState, modifier = Modifier.fillMaxWidth()) {
calendarYears.forEach {
items(it.months.count()) { index ->
CalendarRowItem(
modifier = Modifier.fillParentMaxWidth(),
calendarSize = it.months[index].amountOfDays,
initWeekday = it.months[index].startDayOfMonth.ordinal,
textColor = MaterialTheme.colors.secondaryVariant,
clickedColor = MaterialTheme.colors.primary,
textStyle = MaterialTheme.typography.body1
)
}
}
}
DisposableEffect(Unit) {
coroutineScope.launch {
calendarViewModel.listState.scrollToItem(calendarViewModel.currentMonth)
}
onDispose { }
}
CalendarButtonSection()
}
}
}
calendarViewModel
package com.jens.svensson.jenson_calendar.ui
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import com.jens.svensson.jenson_calendar.data.model.CalendarMonth
import com.jens.svensson.jenson_calendar.data.model.CalendarYear
import com.jens.svensson.jenson_calendar.data.model.YearMonths
import com.jens.svensson.jenson_calendar.domain.repository.CalendarInterface
import com.jens.svensson.jenson_calendar.ui.events.CalendarEvent
import com.jens.svensson.jenson_calendar.ui.state.DropDownMenuState
import dagger.hilt.android.lifecycle.HiltViewModel
import java.time.Month
import java.util.*
import javax.inject.Inject
#HiltViewModel
class CalendarViewModel #Inject constructor(private val repository: CalendarInterface): ViewModel() {
val datesList: List<String> = repository.getShortenedWeekDays()
val calendarYears: List<CalendarYear> = repository.createAndReturnYears()
val standardMonths: List<String> = repository.getStandardMonths()
val listState = LazyListState()
val currentMonth: Int = Calendar.getInstance().get(Calendar.MONTH)
private val _dropdownMenuState = mutableStateOf(DropDownMenuState())
val dropDownMenuState: State<DropDownMenuState> = _dropdownMenuState
init {
_dropdownMenuState.value = dropDownMenuState.value.copy(pickedItem = currentMonth)
}
fun onEvent(event: CalendarEvent){
when(event){
is CalendarEvent.ShowDropDown -> _dropdownMenuState.value = dropDownMenuState.value.copy(showDropDown = event.value)
is CalendarEvent.ClickedMenuItem -> {
_dropdownMenuState.value = _dropdownMenuState.value.copy(pickedItem = event.value)
}
}
}
fun getCalendarMonths(yearIndex: Int): List<CalendarMonth>{
return calendarYears[yearIndex].months
}
}
New viewmodel
package com.jens.svensson.jenson_calendar.ui
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import com.jens.svensson.jenson_calendar.data.model.CalendarMonth
import com.jens.svensson.jenson_calendar.data.model.CalendarYear
import com.jens.svensson.jenson_calendar.data.model.YearMonths
import com.jens.svensson.jenson_calendar.domain.repository.CalendarInterface
import com.jens.svensson.jenson_calendar.ui.events.CalendarEvent
import com.jens.svensson.jenson_calendar.ui.state.DropDownMenuState
import dagger.hilt.android.lifecycle.HiltViewModel
import java.time.Month
import java.util.*
import javax.inject.Inject
#HiltViewModel
class CalendarViewModel #Inject constructor(private val repository: CalendarInterface): ViewModel() {
val datesList: List<String> = repository.getShortenedWeekDays()
val calendarYears: List<CalendarYear> = repository.createAndReturnYears()
val standardMonths: List<String> = repository.getStandardMonths()
val listState = LazyListState()
val currentMonth: Int = Calendar.getInstance().get(Calendar.MONTH)
private var datePickedState: Int = 0
private val _dropdownMenuState = mutableStateOf(DropDownMenuState())
val dropDownMenuState: State<DropDownMenuState> = _dropdownMenuState
init {
_dropdownMenuState.value = dropDownMenuState.value.copy(pickedItem = currentMonth)
}
fun onEvent(event: CalendarEvent){
when(event){
is CalendarEvent.ShowDropDown -> _dropdownMenuState.value = dropDownMenuState.value.copy(showDropDown = event.value)
is CalendarEvent.ClickedMenuItem -> {
_dropdownMenuState.value = _dropdownMenuState.value.copy(pickedItem = event.value)
}
is CalendarEvent.NextMonth -> nextMonth()
is CalendarEvent.PreviousMonth -> previousMonth()
}
}
fun nextMonth(){
datePickedState = _dropdownMenuState.value.pickedItem
if(datePickedState == 11){
datePickedState = 0
}else{
_dropdownMenuState.value = _dropdownMenuState.value.copy(pickedItem = ++datePickedState)
}
}
fun previousMonth(){
datePickedState = _dropdownMenuState.value.pickedItem
if(datePickedState == 0){
datePickedState == 11
}else{
_dropdownMenuState.value = dropDownMenuState.value.copy(pickedItem = --datePickedState)
}
}
fun getCalendarMonths(yearIndex: Int): List<CalendarMonth>{
return calendarYears[yearIndex].months
}
}
The buttons
IconButton(modifier = Modifier.align(Alignment.CenterVertically), onClick = {calendarViewModel.onEvent(CalendarEvent.PreviousMonth)}) {
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = "Go back one month arrow",
tint = calendarColor.mainCalendarTextColor
)
}
IconButton(modifier = Modifier.align(Alignment.CenterVertically), onClick = {
calendarViewModel.onEvent(CalendarEvent.NextMonth)
}) {
Icon(
imageVector = Icons.Default.ArrowForward,
contentDescription = "Go forward one month arrow",
tint = calendarColor.mainCalendarTextColor
)
}
One problem is that i++ passes the current i value to the calculation before increasing, so you pass the same old value to onEvent. You can find more details in this answer - it's about C, but inc/dec operators work the same in all languages they exists. You could've used ++i, which will increase the value before using it in the calculations.
But here comes the second problem. This line:
var dropDownPickedState = calendarViewModel.dropDownMenuState.value.pickedItem
creates a local variable, and those are not saved between recompositions. So the first time you click, you're increasing the local value, but pass the old value to onEvent, which doesn't cause recomposition.
The second time you click - previously increased value gets passed to your view model, which triggers recomposition, and resets your dropDownPickedState.
To prevent such errors don't use var in Compose views, unless you're using it with state delegation, e.g.:
var item by viewModel.stateValue // is ok
var item = viewModel.stateValue.value // is not ok
I'm trying to read a text file using Kotlin from my Assets folder and display it to a Compose text widget. Android Studio Arctic Fox 2020.3
The following code runs successfully and displays the text file to the Output console, however I can't figure out how to get the text file and pass it to a Compose text widget.
You'll notice that I have 2 text() calls inside ReadDataFile(). The first text() is outside of try{} and it works fine, but the text() inside the try{} causes an error: "Try catch is not supported around composable function invocations"
How can I make this work?
Thanks!
package com.learning.kotlinreadfile
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import com.learning.kotlinreadfile.ui.theme.KotlinReadFileTheme
import java.io.InputStream
import java.io.IOException
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
KotlinReadFileTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
ReadDataFile()
}
}
}
}
}
#Composable
fun ReadDataFile() {
println("Read Data File")
Text("Read Data File")
val context = LocalContext.current
try {
val inputStream: InputStream = context.assets.open("data.txt")
val size: Int = inputStream.available()
val buffer = ByteArray(size)
inputStream.read(buffer)
var string = String(buffer)
println(string)
//Text(string) // ERROR: Try catch is not supported around composable function invocations
} catch (e: IOException) {
e.printStackTrace()
println("Error")
}
}
Caution
File read (I/O) operations can be long, so it is not recommended to use the UI scope to read files. But that's not what's causing the problem, I'm just warning you that if it reads very large files, it can make your app crash because it does a very long processing in the UI thread. I recommend checking this link if you're not familiar with this type of problem.
Problem solving following best practices
Fortunately Jetpack compose works very well with reactive programming, so we can take advantage of that to write reactive code that doesn't face the aforementioned problems.
I made an example very similar to yours, I hope you can understand:
UiState file
As stated earlier, reading a file can be a long process, so let's imagine 3 possible states, "loading", "message successful" and "error". In the case of "successful message" we will have a possibly null string that will no longer be null when the message is actually read from the txt file:
package com.example.kotlinreadfile
data class UiState(
val isLoading: Boolean,
val isOnError: Boolean,
val fileMessage: String?
)
MainActivity.kt file
Here will be just our implementation of the UI, in your case the text messages arranged in the application. As soon as we want to read these messages on the screen we will make a request to our ViewModel:
package com.example.kotlinreadfile
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.layout.*
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import com.example.kotlinreadfile.ui.theme.KotlinReadFileTheme
class MainActivity : ComponentActivity() {
private val viewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
KotlinReadFileTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
val context = LocalContext.current
viewModel.loadData(context)
ScreenContent(viewModel.uiState.value)
}
}
}
}
#Composable
fun ScreenContent(uiState: UiState) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Text(text = "Read Data File")
Spacer(modifier = Modifier.height(8.dp))
when {
uiState.isLoading -> CircularProgressIndicator()
uiState.isOnError -> Text(text = "Error when try load data from txt file")
else -> Text(text = "${uiState.fileMessage}")
}
}
}
}
MainViewModel.kt file
If you are unfamiliar with the ViewModel class I recommend this official documentation link.
Here we will focus on "our business rule", what we will actually do to get the data. Since we are dealing with an input/output (I/O) operation we will do this in an appropriate scope using viewModelScope.launch(Dispatchers.IO):
package com.example.kotlinreadfile
import android.content.Context
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.IOException
import java.io.InputStream
class MainViewModel : ViewModel() {
private val _uiState = mutableStateOf(
UiState(
isLoading = true,
isOnError = false,
fileMessage = null
)
)
val uiState: State<UiState> = _uiState
fun loadData(context: Context) {
viewModelScope.launch(Dispatchers.IO) {
try {
val inputStream: InputStream = context.assets.open("data.txt")
val size: Int = inputStream.available()
val buffer = ByteArray(size)
inputStream.read(buffer)
val string = String(buffer)
launch(Dispatchers.Main) {
_uiState.value = uiState.value.copy(
isLoading = false,
isOnError = false,
fileMessage = string
)
}
} catch (e: IOException) {
e.printStackTrace()
launch(Dispatchers.Main) {
_uiState.value = uiState.value.copy(
isLoading = false,
isOnError = true,
fileMessage = null
)
}
}
}
}
}
jetpack compose by state refresh, please try
#Preview
#Composable
fun ReadDataFile() {
var dataText by remember {
mutableStateOf("asd")
}
println("Read Data File")
Column {
Text("Read Data File")
Text(dataText)
}
val context = LocalContext.current
LaunchedEffect(true) {
kotlin.runCatching {
val inputStream: InputStream = context.assets.open("data.txt")
val size: Int = inputStream.available()
val buffer = ByteArray(size)
inputStream.read(buffer)
String(buffer)
}.onSuccess {
it.logE()
dataText = it
}.onFailure {
dataText = "error"
}
}
}
my app working fine any without problem , i used AsyncTask to get data json and every thing is fine . i want to add code to check internet connection in my app and i put code under onCreate in main activity .
val cm = baseContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networkInfo = cm.activeNetworkInfo
if (networkInfo != null && networkInfo.isConnected){
if (networkInfo.type == ConnectivityManager.TYPE_WIFI){
Toast.makeText(baseContext,"wifi",Toast.LENGTH_SHORT).show()
}
if (networkInfo.type == ConnectivityManager.TYPE_MOBILE){
Toast.makeText(baseContext,"MOBILE",Toast.LENGTH_SHORT).show()
}
}else {
Toast.makeText(baseContext,"MOBILE",Toast.LENGTH_SHORT).show()
this.finish()
}
when l put the phone on airplan mode and launching app he is stop working . and crash .
console log
E/AndroidRuntime: FATAL EXCEPTION: AsyncTask #1
Process: com.iraqairoirt.iraqairports, PID: 10868
main activity
package com.iraqairoirt.iraqairports
import android.annotation.SuppressLint
import android.content.Context
import android.content.DialogInterface
import android.os.AsyncTask
import android.os.Bundle
import android.support.design.widget.NavigationView
import android.support.v4.view.GravityCompat
import android.support.v7.app.ActionBarDrawerToggle
import android.support.v7.app.AlertDialog
import android.support.v7.app.AppCompatActivity
import android.view.Menu
import android.view.MenuItem
import android.widget.TextView
import com.iraqairoirt.iraqairports.BaghdadAirport.ListAdapteArr
import com.iraqairoirt.iraqairports.BaghdadAirport.ListAdapteDep
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.content_main.*
import kotlinx.android.synthetic.main.fragment_baghdada_arrivel.*
import kotlinx.android.synthetic.main.fragment_baghdada_dep.*
import org.json.JSONArray
import org.json.JSONObject
import java.net.HttpURLConnection
import java.net.URL
import android.widget.Toast
import android.content.Intent
import android.content.IntentFilter
import android.net.ConnectivityManager
import android.net.NetworkInfo
import android.support.design.widget.Snackbar
import com.iraqairoirt.iraqairports.BaghdadAirport.FlightsArrivelBeforBGW
import com.iraqairoirt.iraqairports.BaghdadAirport.FlightsDepBeforBGW
class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val url = "airport.json"
Arr().execute(url)
setSupportActionBar(toolbar)
val fragmentAdapter = MyPagerAdapter(supportFragmentManager)
viewpager_main.adapter = fragmentAdapter
sliding_tabs.setupWithViewPager(viewpager_main)
val toggle = ActionBarDrawerToggle(
this, drawer_layout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close
)
drawer_layout.addDrawerListener(toggle)
toggle.syncState()
nav_view.setNavigationItemSelectedListener(this)
val cm = baseContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networkInfo = cm.activeNetworkInfo
if (networkInfo != null && networkInfo.isConnected){
if (networkInfo.type == ConnectivityManager.TYPE_WIFI){
Toast.makeText(baseContext,"wifi",Toast.LENGTH_SHORT).show()
}
if (networkInfo.type == ConnectivityManager.TYPE_MOBILE){
Toast.makeText(baseContext,"MOBILE",Toast.LENGTH_SHORT).show()
}
}else {
Toast.makeText(baseContext,"MOBILE",Toast.LENGTH_SHORT).show()
this.finish()
}
}
// full class for json api
inner class Arr : AsyncTask<String, String, String>() {
val progressDialog = AlertDialog.Builder(this#MainActivity)
val dialogView = layoutInflater.inflate(R.layout.progress_dialog, null)
val message = dialogView.findViewById<TextView>(R.id.message_id)
val dialog = progressDialog.create()
override fun onPreExecute() {
super.onPreExecute()
dialog.setMessage("يرجى الانتظار")
dialog.setCancelable(false)
dialog.show()
}
// for build connection
override fun doInBackground(vararg url: String?): String {
var text: String
val connection = URL(url[0]).openConnection() as HttpURLConnection
try {
connection.connect()
text = connection.inputStream.use { it.reader().use { reader -> reader.readText() } }
} finally {
connection.disconnect()
}
return text
}
override fun onPostExecute(result: String?) {
super.onPostExecute(result)
handleJson(result)
dialog.dismiss();
}
override fun onProgressUpdate(vararg text: String?) {
}
#SuppressLint("WrongViewCast")
private fun handleJson(jsonString: String?) {
val jsonObj = JSONObject(jsonString)
val result = jsonObj.getJSONObject("result")
val response = result.getJSONObject("response")
val airport = response.getJSONObject("airport")
val pluginData = airport.getJSONObject("pluginData")
val schedule = pluginData.getJSONObject("schedule")
val arrivals = schedule.getJSONObject("arrivals")
// weather data
val weather = pluginData.getJSONObject("weather")
val mater = weather.getString("metar")
// MaterText.text=mater
// val data = arrivals.getJSONObject("data")
val jsonArray = JSONArray(arrivals.get("data").toString())
val list = ArrayList<FlightShdu>()
var x = 0
while (x < jsonArray.length()) {
val jsonObject = jsonArray.getJSONObject(x)
list.add(
FlightShdu(
jsonObject.getJSONObject("flight").getJSONObject("identification").getJSONObject("number").getString(
"default"
),
jsonObject.getJSONObject("flight").getJSONObject("airline").getString("short"),
jsonObject.getJSONObject("flight").getJSONObject("status").getJSONObject("generic").getJSONObject(
"status"
).getString("text"),
jsonObject.getJSONObject("flight").getJSONObject("airline").getJSONObject("code").getString("icao"),
jsonObject.getJSONObject("flight").getJSONObject("time").getJSONObject("scheduled").getString("arrival"),
jsonObject.getJSONObject("flight").getJSONObject("airport").getJSONObject("origin").getJSONObject(
"code"
).getString("iata"),
jsonObject.getJSONObject("flight").getJSONObject("aircraft").getJSONObject("model").getString("code"),
// for more information
jsonObject.getJSONObject("flight").getJSONObject("time").getJSONObject("real").getString("departure"),
jsonObject.getJSONObject("flight").getJSONObject("time").getJSONObject("estimated").getString("arrival"),
// jsonObject.getJSONObject("flight").getJSONObject("time").getJSONObject("estimated").getString("arrival"),
jsonObject.getJSONObject("flight").getJSONObject("aircraft").getString("registration"),
jsonObject.getJSONObject("flight").getJSONObject("status").getJSONObject("generic").getJSONObject(
"status"
).getString("diverted"),
arrivals.getString("timestamp"),
jsonObject.getJSONObject("flight").getJSONObject("status").getString("icon")
)
)
x++
}
list.forEach(::println)
var adapter = ListAdapteArr(this#MainActivity, list)
flight_arrivel_list.adapter = adapter
}
}
override fun onBackPressed() {
if (drawer_layout.isDrawerOpen(GravityCompat.START)) {
drawer_layout.closeDrawer(GravityCompat.START)
} else {
super.onBackPressed()
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.main, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val id = item.itemId
//noinspection SimplifiableIfStatement
if (id == R.id.flightarrbeforbgw) {
val intent = Intent(this, FlightsArrivelBeforBGW::class.java)
this.startActivity(intent)
return true
}
if (id == R.id.flightdepbefrobgw) {
val intent = Intent(this, FlightsDepBeforBGW::class.java)
this.startActivity(intent)
return true
}
//
// if (id == R.id.searchflights) {
// Toast.makeText(this, "Android Menu is Clicked", Toast.LENGTH_LONG).show()
// return true
// }
return super.onOptionsItemSelected(item)
}
override fun onNavigationItemSelected(item: MenuItem): Boolean {
// Handle navigation view item clicks here.
when (item.itemId) {
R.id.nav_camera -> {
// Handle the camera action
}
R.id.nav_gallery -> {
}
R.id.nav_slideshow -> {
}
R.id.nav_manage -> {
}
R.id.nav_share -> {
}
R.id.nav_send -> {
}
}
drawer_layout.closeDrawer(GravityCompat.START)
return true
}
}
to avoid fatal error and crash when there is not internet connection ,my mistake is i should put execute() url inside code of check internet connection
val cm = baseContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networkInfo = cm.activeNetworkInfo
if (networkInfo != null && networkInfo.isConnected){
if (networkInfo.type == ConnectivityManager.TYPE_WIFI){
val url = "airport.json"
Arr().execute(url)
}
if (networkInfo.type == ConnectivityManager.TYPE_MOBILE){
val url = "airport.json"
Arr().execute(url)
}
}else {
val builder = AlertDialog.Builder(this)
builder.setTitle("No internet Connection")
builder.setMessage("Please turn on internet connection to continue")
builder.setNegativeButton(
"close"
) { dialog, button -> this.finish() }
val alertDialog = builder.create()
alertDialog.show()
}
now my app working fine =)