How to take pictures using only the front camera in jetpack compose? - android-jetpack-compose

I am trying to take pictures from front camera using jetpack compose. I tried it using LocalContext.current.getCameraProvider() but it opens only the back camera. I do not want to open the back camera. I tried this to open the camera but I am unable to open the front camera.
I am new to jetpack compose, someone please help me.

This should be the way:
#Composable
fun CameraView(onImageCaptured: (Uri, Boolean) -> Unit, onError: (ImageCaptureException) -> Unit) {
val context = LocalContext.current
var lensFacing by remember { mutableStateOf(CameraSelector.LENS_FACING_BACK) }
val imageCapture: ImageCapture = remember {
ImageCapture.Builder().build()
}
val galleryLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.GetContent()
) { uri: Uri? ->
if (uri != null) onImageCaptured(uri, true)
}
CameraPreviewView(
imageCapture,
lensFacing
) { cameraUIAction ->
when (cameraUIAction) {
is CameraUIAction.OnCameraClick -> {
imageCapture.takePicture(context, lensFacing, onImageCaptured, onError)
}
is CameraUIAction.OnSwitchCameraClick -> {
lensFacing =
if (lensFacing == CameraSelector.LENS_FACING_BACK) CameraSelector.LENS_FACING_FRONT
else
CameraSelector.LENS_FACING_BACK
}
is CameraUIAction.OnGalleryViewClick -> {
if (true == context.getOutputDirectory().listFiles()?.isNotEmpty()) {
galleryLauncher.launch("image/*")
}
}
}
}
}

Related

Jetpack Compose - how to check if keyboard is open or closed

I'm using Jetpack Compose and trying to find a way to detect if the keyboard is open.
I've tried to use the below code, but I get an error stating Unresolved reference: ime. When I click on the recommended imports (the 2 shown below), this error still remains.
import android.view.WindowInsets
import android.view.WindowInsets.Type.ime
#Composable
fun signInView() {
val isVisible = WindowInsets.ime.getBottom(LocalDensity.current) > 0
}
How can I resolve this?
Add the dependencies for the artifacts you need in the build.gradle file for your app or module:
dependencies {
implementation "androidx.compose.foundation:foundation:1.3.1"
}
android {
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.3.2"
}
kotlinOptions {
jvmTarget = "1.8"
}
}
Example:
#Composable
fun signInView() {
var isVisible by remember { mutableStateOf(false) }
val ime = androidx.compose.foundation.layout.WindowInsets.ime
val navbar = androidx.compose.foundation.layout.WindowInsets.navigationBars
var keyboardHeightDp by remember { mutableStateOf(0.dp) }
val localDensity = LocalDensity.current
LaunchedEffect(localDensity.density) {
snapshotFlow {
ime.getBottom(localDensity) - navbar.getBottom(localDensity)
}.collect {
val currentKeyboardHeightDp = (it / localDensity.density).dp
keyboardHeightDp = maxOf(currentKeyboardHeightDp, keyboardHeightDp)
isVisible = currentKeyboardHeightDp == keyboardHeightDp
}
}
}

Load next data when on click of button using the Paging 3 library for Compose

Currently Paging library handles when to load the data automatically when a user scrolls down. But what if you want to give the user full authority for when they want the next page of data to be loaded i.e when button is clicked show next page of movies. How can you handle this in Paging library? See below how I've implemented the paging to load data as a user scrolls down
Here below this how I implemented the Paging to load next page when user scrolls down
class MoviesPagingDataSource(
private val repo: MoviesRepository,
) : PagingSource<Int, Movies>() {
override fun getRefreshKey(state: PagingState<Int, Movies>): Int? {
return state.anchorPosition?.let { anchorPosition ->
val anchorPage = state.closestPageToPosition(anchorPosition)
anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
}
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Movies> {
return try {
val nextPageNumber = params.key ?: 0
val response = repo.getMovies(page = nextPageNumber, size = 10)
LoadResult.Page(
data = response.content,
prevKey = null,
nextKey = if (response.content.isNotEmpty()) response.number + 1 else null
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
}
This is how I emit the state in ViewModel for the UI to observe
#HiltViewModel
class MoviesViewModel #Inject constructor(
private val moviesRepository: MoviesRepository
): ViewModel {
....
//emitting the data to the UI to observe
val moviesPagingDataSource = Pager(PagingConfig(pageSize = 10)) {
MoviesPagingDataSource(moviesRepository)
}.flow.cachedIn(viewModelScope)
}
How I'm observing it in the UI
#Composable
fun MoviesList(viewModel: MoviesViewModel) {
val moviesList = viewModel.moviesPagingDataSource.collectAsLazyPagingItems()
LazyColumn {
items(moviesList) { item ->
item?.let { MoviesCard(movie = it) }
}
when (moviesList.loadState.append) {
is LoadState.NotLoading -> Unit
LoadState.Loading -> {
item {
LoadingItem()
}
}
is LoadState.Error -> {
item {
ErrorItem(message = "Some error occurred")
}
}
}
when (moviesList.loadState.refresh) {
is LoadState.NotLoading -> Unit
LoadState.Loading -> {
item {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Center
) {
CircularProgressIndicator()
}
}
}
is LoadState.Error -> TODO()
}
}
}
So currently I'm adding 1 to the previous page every time a user clicks the button to load more movies then saving this movies to the list state. Also making sure the current page is greater than or equal to total pages before loading more data and adding to the list state of previous loaded movies
you can use Channel to block LoadResult returning from load, when user clicks the button, send an element to the Channel. here is the simple
class MoviesPagingDataSource(
private val repo: MoviesRepository
) : PagingSource<Int, Movies>() {
private val channel: Channel<Unit> = Channel(1, BufferOverflow.DROP_LATEST)
override fun getRefreshKey(state: PagingState<Int, Movies>): Int? {
return state.anchorPosition?.let { anchorPosition ->
val anchorPage = state.closestPageToPosition(anchorPosition)
anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
}
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Movies> {
return try {
val nextPageNumber = params.key ?: 0
// load initial page
if (nextPageNumber == 0) loadNextPage()
val response = repo.getMovies(page = nextPageNumber, size = 10)
/*
* block next page data return, when user click the button,
* call loadNextPage. if you don't want to automatically
* request data as the user scrolls toward the end of the
* loaded data. put this line above the repo.getMovies.
**/
channel.receive()
LoadResult.Page(
data = response.content,
prevKey = null,
nextKey = if (response.content.isNotEmpty()) response.number + 1 else null
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
suspend fun loadNextPage() {
channel.send(Unit)
}
}

Android dataStore with flow not get update after edit

I'm use DataStore with flow but I cant get any update on the flow when editing DataStore.
Store.kt
private class IStore(private val context: Context): Store {
val eventIDKey = stringPreferencesKey("EventID")
override suspend fun setEventID(eventID: String) {
context.dataStoreSettings.edit { settings ->
settings[eventIDKey] = eventID
}
}
override fun getEventID(): Flow<String> {
return context.dataStoreSettings.data.map { settings -> settings[eventIDKey].orEmpty() }
}
}
and manipulate getEventID() with data from room database in event service
EventService.kt
fun getSelectedEventLive() = store.getEventID()
.onEach { Log.d("EventService", "income new event id $it") }
.flatMapConcat { if(it.isNotBlank()) eventDao.get(it) else flowOf(null) }
onEach called when I collect the data but when updated it's not called again and need to close and open the app to show the latest data
MainViewModel.kt
val selectedEvent = eventService.getSelectedEventLive()
.stateIn(viewModelScope, SharingStarted.Lazily, null)
and use on Compose with this
val currentEvent by mainViewModel.selectedEvent.collectAsState()
Maybe I doing wrong or maybe there is something I miss?
Usually, you want to use flow.collect {...}, since Flow is cold and need to know that it is being collected to start producing new values.
// MainViewModel.kt
private val _selectedEvent = MutableStateFlow<TypeOfYourEvent>()
val selectedEvent: StateFlow<TypeOfYourEvent> = _selectedEvent
init {
viewModelScope.launch {
getSelectedEventLive().collect { it ->
_selectedEvent.value = it
}
}
}
This example should be fine with your composable's code, you still can collect selectedEvent as state.
Yeah i found the solusion its works if i change the flatMapConcat with flatMapLatest in EventService.kt
fun getSelectedEventLive() = store.getEventID()
.filterNot { it.isBlank() }
.flatMapLatest { eventDao.get(it) }

Jetpack Compose - Speech Recognition

Do you know how to apply Speech Recognition (SpeechRecognizer) in Jetpack Compose?
Something like this, but in Compose.
I followed the steps in this video:
Added these permissions in the manifest:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
Wrote this code in MainActivity:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
PageUi()
}
}
}
#Composable
fun PageUi() {
val context = LocalContext.current
val talk by remember { mutableStateOf("Speech text should come here") }
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = talk,
style = MaterialTheme.typography.h4,
modifier = Modifier
.fillMaxSize(0.85f)
.padding(16.dp)
.background(Color.LightGray)
)
Button(onClick = { askSpeechInput(context) }) {
Text(
text = "Talk", style = MaterialTheme.typography.h3
)
}
}
}
fun askSpeechInput(context: Context) {
if (!SpeechRecognizer.isRecognitionAvailable(context)) {
Toast.makeText(context, "Speech not available", Toast.LENGTH_SHORT).show()
} else {
val i = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)
i.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM)
i.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault())
i.putExtra(RecognizerIntent.EXTRA_PROMPT, "Talk")
//startActivityForResult(MainActivity(),i,102)
}
}
#Preview(showBackground = true)
#Composable
fun PageShow() {
PageUi()
}
But I have no idea how to use startActivityForResult in Compose and do the rest?
And when I test it so far on my phone (or emulator) it always ends up with the toast message!
I am going to explain my own implementation. Let me give you a general idea first, and then I am going to explain each step. So first you need to ask for permissions every time and then if permission is granted then you should start an intent in order to hear what the user says. What the user says is saved on a variable to a View Model. The variable on the View Model is being observed by the composable so you can get the data.
1) Add this to your Manigest:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="your.package">
// Add uses-permission
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
[...]
[...]
[...]
// Add above the last line </manifest> like so:
<queries>
<intent>
<action android:name="android.speech.RecognitionService" />
</intent>
</queries>
</manifest>
2) Create a ViewModel
class ScreenViewModel : ViewModel() {
var textFromSpeech: String? by mutableStateOf(null)
}
You need the ViewModel in order to observe the variable from composable and implement your code logic for clean architecture.
3) Implement asking for permission
In build.gradle add the following:
implementation "com.google.accompanist:accompanist-permissions:$accompanist_version"
Then create a composable like so:
#ExperimentalPermissionsApi
#Composable
fun OpenVoiceWithPermission(
onDismiss: () -> Unit,
vm: ScreenViewModel,
ctxFromScreen: Context,
finished: () -> Unit
) {
val voicePermissionState = rememberPermissionState(android.Manifest.permission.RECORD_AUDIO)
val ctx = LocalContext.current
fun newIntent(ctx: Context) {
val intent = Intent()
intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
val uri = Uri.fromParts(
"package",
BuildConfig.APPLICATION_ID, null
)
intent.data = uri
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
ctx.startActivity(intent)
}
PermissionRequired(
permissionState = voicePermissionState,
permissionNotGrantedContent = {
DialogCustomBox(
onDismiss = onDismiss,
dialogBoxState = DialogLogInState.REQUEST_VOICE,
onRequestPermission = { voicePermissionState.launchPermissionRequest() }
)
},
permissionNotAvailableContent = {
DialogCustomBox(
onDismiss = onDismiss,
dialogBoxState = DialogLogInState.VOICE_OPEN_SYSTEM_SETTINGS,
onOpenSystemSettings = { newIntent(ctx) }
)
}
) {
startSpeechToText(vm, ctxFromScreen, finished = finished)
}
}
DialogBox you can create your own custom as I have done or use the standard version, this is up to you and out of the scope of this answer.
On the above code if permission is granted you move automatically to this piece of code: startSpeechToText(vm, ctxFromScreen, finished = finished) which you have to implement next.
4) Implementing Speech Recognizer
fun startSpeechToText(vm: ScreenViewModel, ctx: Context, finished: ()-> Unit) {
val speechRecognizer = SpeechRecognizer.createSpeechRecognizer(ctx)
val speechRecognizerIntent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)
speechRecognizerIntent.putExtra(
RecognizerIntent.EXTRA_LANGUAGE_MODEL,
RecognizerIntent.LANGUAGE_MODEL_FREE_FORM,
)
// Optionally I have added my mother language
speechRecognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, "el_GR")
speechRecognizer.setRecognitionListener(object : RecognitionListener {
override fun onReadyForSpeech(bundle: Bundle?) {}
override fun onBeginningOfSpeech() {}
override fun onRmsChanged(v: Float) {}
override fun onBufferReceived(bytes: ByteArray?) {}
override fun onEndOfSpeech() {
finished()
// changing the color of your mic icon to
// gray to indicate it is not listening or do something you want
}
override fun onError(i: Int) {}
override fun onResults(bundle: Bundle) {
val result = bundle.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION)
if (result != null) {
// attaching the output
// to our viewmodel
vm.textFromSpeech = result[0]
}
}
override fun onPartialResults(bundle: Bundle) {}
override fun onEvent(i: Int, bundle: Bundle?) {}
})
speechRecognizer.startListening(speechRecognizerIntent)
}
With this implementation it is very customizable and you do not get this pop up from google. So you can inform the user that his device is listening with your own unique way!
5) Call from your composable the function to start listening:
#ExperimentalPermissionsApi
#Composable
fun YourScreen() {
val ctx = LocalContext.current
val vm: ScreenViewModel = viewModel()
var clickToShowPermission by rememberSaveable { mutableStateOf(false) }
if (clickToShowPermission) {
OpenVoiceWithPermission(
onDismiss = { clickToShowPermission = false },
vm = vm,
ctxFromScreen = ctx
) {
// Do anything you want when the voice has finished and do
// not forget to return clickToShowPermission to false!!
clickToShowPermission = false
}
}
}
So on you code everytime you call clickToShowPermission = true you can start listening what the user says...
use registerForActivityResult(ActivityResultContract, ActivityResultCallback) passing in a androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult object for the ActivityResultContract.
By declaring the StartActivityForResult callback function
val startLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {it ->
//you implement
}
start Intent
startLauncher.launch(intent)
test simple example
#Composable
fun TestStartForResult() {
val content = LocalContext.current
val startLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult())
{
Toast.makeText(content, "Result", Toast.LENGTH_SHORT).show()
}
Button(onClick = {
startLauncher.launch(Intent(content,TestActivity::class.java))
}) {
Text("start")
}
}

Jetpack compose how to recompose when coming to foreground

I'd like to make status bar hidden and I've managed to do it like so using the accompanist library:
val systemUiController = rememberSystemUiController()
systemUiController.isStatusBarVisible = false
The issue is that when the app goes to background and comes to foreground, this piece of code is not run and therefore the status bar is shown again. How can I fix that?
Thanks.
You can use OnLifecycleEvent from this answer.
val systemUiController = rememberSystemUiController()
OnLifecycleEvent { _, event ->
when (event) {
Lifecycle.Event.ON_RESUME,
Lifecycle.Event.ON_START,
-> {
systemUiController.isStatusBarVisible = false
}
else -> Unit
}
}
OnLifecycleEvent:
#Composable
fun OnLifecycleEvent(onEvent: (owner: LifecycleOwner, event: Lifecycle.Event) -> Unit) {
val eventHandler = rememberUpdatedState(onEvent)
val lifecycleOwner = rememberUpdatedState(LocalLifecycleOwner.current)
DisposableEffect(lifecycleOwner.value) {
val lifecycle = lifecycleOwner.value.lifecycle
val observer = LifecycleEventObserver { owner, event ->
eventHandler.value(owner, event)
}
lifecycle.addObserver(observer)
onDispose {
lifecycle.removeObserver(observer)
}
}
}

Resources