I have a jetpack compose app in which there's a video player. The app is forced to portrait orientation, except for the screen/composable containing the video player. That composable is forced to landscape because I want to display the videos in landscape.
When I navigate to the page containing the video player the app correctly changes the orientation to landscape, but as soon as the video starts playing it rotates back to portrait orientation.
Any help is greatly appreciated...
Here's the relevant code for my problem.
#Composable
fun LockScreenOrientation(orientation: Int) {
val context = LocalContext.current
DisposableEffect(Unit) {
val activity = context.findActivity() ?: return#DisposableEffect onDispose {}
val originalOrientation = activity.requestedOrientation
activity.requestedOrientation = orientation
onDispose {
// restore original orientation when view disappears
activity.requestedOrientation = originalOrientation
}
}
}
fun Context.findActivity(): Activity? = when (this) {
is Activity -> this
is ContextWrapper -> baseContext.findActivity()
else -> null
}
#Composable
fun VideoView() {
val exoPlayer = remember {
ExoPlayer.Builder(context)
.build()
.apply {
setMediaItem(
MediaItem.Builder()
.apply {
setUri("https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4")
setMediaMetadata(
MediaMetadata.Builder()
.build()
)
}
.build()
)
prepare()
playWhenReady = true
}
}
DisposableEffect(
AndroidView(factory = {
PlayerView(context).apply {
player = exoPlayer
useController = false
layoutParams = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
}
})
) {
onDispose { exoPlayer.release() }
}
}
#Composable
fun VideoScreen() {
LockScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
RakeVragenTheme {
Surface(
modifier = Modifier
.fillMaxSize()
color = Color.Black
) {
VideoView()
}
}
}
Related
I am just playing around with navigation compose and trying to figure out how it works. I read some articles and watch tutorials how to implement it in my app. So I choose the simpliest way to do this, but when I clicked the buttot to navigate to second screen, app crashed and exited. What am I doing wrong?
I am not doing any fancy stuff like bottom navigation, splash screens and etc, just navigate to the second screen.
Here I created navigation's logic
#Composable
fun navigationDraft(navController: NavController) {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = ScreenNavigation.Home.routeName
) {
composable(route = ScreenNavigation.Home.routeName) {
Home( navController = navController)
}
composable(route = ScreenNavigation.DetailedScreen.routeName) {
DetailedScreen(navController = navController)
}
}
}
Here I created navigation's route:
sealed class ScreenNavigation(var routeName: String, ){
object Home : ScreenNavigation(routeName = "home")
object DetailedScreen : ScreenNavigation(routeName = "detailed")
}
HomeScreen:
#Composable
fun Home(navController: NavController) {
Button(onClick = {navController.navigate(ScreenNavigation.DetailedScreen.routeName) }) {
}
}
Detailed Screen
#Composable
fun DetailedScreen(navController: NavController) {
Scaffold() {
TopAppBar(elevation = 2.dp, backgroundColor = Color.Magenta) {
Text(text = "Second Screen With Detail", fontStyle = FontStyle.Italic)
}
Column(verticalArrangement = Arrangement.Center) {
Text(text = "Hi", fontSize = 30.sp)
}
}
}
MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Users_plofile_kotlinTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
val navController = rememberNavController()
Home(navController = navController)
nameViewModel.getUserNameList()
}
}
}
The error I have got:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.users_plofile_kotlin, PID: 24321
java.lang.NullPointerException
at androidx.navigation.NavController.navigate(NavController.kt:1652)
at androidx.navigation.NavController.navigate(NavController.kt:1984)
Ok, I think I found the issue. I created another #Composable MainScreen function instead of default #Composable Greetings function and put there all routes I would like to use, so my code now a little bit fixed but the main idea is still the same:
This should be instead of #Composable Greeting function
#Composable
fun MainScreen(){
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "home"
) {
composable(route = ScreenNavigation.ButtonToCkeck.route) {
Home(navController = navController )
}
composable(route = ScreenNavigation.DetailedSreen.route) {
DetailedSreen()
}
}
}
And put it in the MainAcrivity:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
NavigationAppTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
MainScreen()
}
}
}
}
}
Now it works smoothly. Also the version of navigation composable is:
implementation("androidx.navigation:navigation-compose:2.5.1")
I hope to call nativeCanvas.drawText periodically to draw a text with Jetpack Compose.
But I get Result A when I run Code A.
1: What's wrong with my code?
2: How can I nativeCanvas.drawText a text periodically ?
Code A
#Composable
fun ScreenHome_Watch(
){
Box(
) {
Canvas(
) {
startTimer{
drawIntoCanvas {
it.nativeCanvas.drawText("Hello "+Calendar.getInstance().time.toString() ,10f,10f, paintText)
}
}
}
}
}
private lateinit var timer: Timer
fun startTimer(block: ()->Unit) {
timer = Timer()
timer.scheduleAtFixedRate(timerTask {
block()
}, 0, 1000)
}
Result A
java.lang.ClassCastException: androidx.compose.ui.graphics.drawscope.EmptyCanvas cannot be cast to androidx.compose.ui.graphics.AndroidCanvas
No need to break the mechanism of the composition. You must change the state and compose will process the changes
#Composable
fun ScreenHome_Watch() {
var currentTime by remember { mutableStateOf(Calendar.getInstance().time) }
val paint = remember {
Paint().apply {
isAntiAlias = true
textSize = 24f
typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD)
}
}
Canvas(modifier = Modifier.size(height = 20.dp, width = 180.dp)) {
drawIntoCanvas {
it.nativeCanvas.drawText(
"Hello $currentTime", 30f, 30f, paint
)
}
}
LaunchedEffect(Unit) {
while (true) {
currentTime = Calendar.getInstance().time
delay(1000)
}
}
}
Whenever i want to navigate to other screen i want my MediaPlayer should Stop where is best place to using the stop() function .
#Composable
fun MenuScreen(navController: NavController) {
val context = LocalContext.current
val menuMusic : MediaPlayer = MediaPlayer.create(context , R.raw.menu_music)
menuMusic.isLooping = true
menuMusic.start()
Box(
modifier = Modifier
.fillMaxSize()
.background(color = darkBackground)
) {...}
If you want your MediaPlayer to start when your composable enters the composition and stop when it leaves the composition, you would do this:
val context = LocalContext.current
val menuMusic: MediaPlayer = remember {
MediaPlayer.create(context, R.raw.menu_music)
}
DisposableEffect(Unit) {
menuMusic.isLooping = true
menuMusic.start()
onDispose {
menuMusic.stop()
}
}
I discovering android Cetpack Compose (and Navigation) and try to display a preview of a view with a navController as parameter.
To achieve, I use the PreviewParameter and I have no error, but nothing displayed in the Preview window.
Anyone know how pass a fake NavController instance to a Composable?
class FakeNavController : PreviewParameterProvider<NavController> {
override val values: Sequence<NavController>
get() {}
}
#Preview
#Composable
fun Preview(
#PreviewParameter(FakeNavController::class) fakeNavController: NavController
) {
HomeView(fakeNavController)
}
You don't have to make it nullable and pass null to it.
You just need to pass this: rememberNavController()
#Preview
#Composable
fun Preview() {
HomeView(rememberNavController())
}
PreviewParameter is used to create multiple previews of the same view with different data. You're expected to return the needed values from values. In your example you return nothing that's why it doesn't work(it doesn't even build in my case).
That won't help you to mock the navigation controller, as you still need to create it somehow to return from values. I think it's impossible.
Instead you can pass a handler, in this case you don't need to mock it:
#Composable
fun HomeView(openOtherScreen: () -> Unit) {
}
// your Navigation view
composable(Destinations.Home) { from ->
HomeView(
openOtherScreen = {
navController.navigate(Destinations.Other)
},
)
}
Finally, I declare a nullable NavController and it works.
#Composable
fun HomeView(navController: NavController?) {
Surface {
Column(
modifier = Modifier
.padding(all = 4.dp)
) {
Text(
text = "Home View",
style = MaterialTheme.typography.body2
)
Spacer(modifier = Modifier.height(8.dp))
Button(onClick = { navController?.navigate("lineRoute") }) {
Text(text = "nav to Line view")
}
}
}
}
#Preview
#Composable
fun Preview (){
HomeView(null)
}
In my app there's a LazyColumn that contains nested LazyRows. I have a memory issue - when there are 30-40 rows and about 10-20 elements per row in the grid, it's possible to reach Out-of-Memory (OOM) by simply scrolling the list vertically up and down about 20 times. An item is a Card with some Boxes and texts. It seems that the resulting composable for each of the items is stored, even when the item is out of composition.
Here is a sample that demonstrates this. It shows a simple grid of 600 elements (they are just Text) and on my emulator gets to a memory usage of about 200 MB. (I use Android TV emulator with landscape, 120 elements are visible at once).
MainActivity.kt
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
LazyColumnTestTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
runTest()
}
}
}
}
}
#Composable
fun runTest() {
var itemsState: MutableState<List<TestDataBlock>> = remember {
mutableStateOf(listOf())
}
LaunchedEffect(Unit) {
delay(1000)
itemsState.value = MutableList<TestDataBlock>(30) { rowIndex ->
val id = rowIndex
TestDataBlock(id = id.toString(), data = 1)
}
}
List(dataItems = itemsState.value)
}
#Preview
#Composable
fun List(
dataItems: List<TestDataBlock> = listOf(TestDataBlock("1",1), TestDataBlock("2",2))
) {
LazyColumn(
modifier = Modifier
.fillMaxSize()
) {
itemsIndexed(items = dataItems,
key = { _, item ->
item.id
}
) { _, rowItem ->
drawElement(rowItem)
}
}
}
#Composable
fun drawElement(rowItem: TestDataBlock) {
Text(text = "${rowItem.id}")
LazyRow() {
itemsIndexed(items = rowItem.testDataItems,
key = { _, item ->
item.id
}
) { _, item ->
Text(text = "${item.id }", color = Color.Black, modifier = Modifier.width(100.dp))
}
}
}
TestDataBlock.kt
#Immutable
data class TestDataBlock(
val id: String,
val data: Int,
) {
val testDataItems: List<TestDataItem> = (0..20).toList().map{ TestDataItem(it.toString()) }
}
TestDataItem.kt
#Immutable
data class TestDataItem(
val id: String
)