https://kotlinlang.org logo
Title
m

Manuel Lorenzo

08/24/2022, 8:16 AM
hi all! how can I avoid Android to request the permissions again when the phone is rotated?
for example in my
MainActivity
I have this:
override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            PSPDFKitAssignmentTheme {
                MainScreen()
            }
        }
    }

    @OptIn(ExperimentalAnimationApi::class)
    @SuppressLint("UnusedMaterialScaffoldPaddingParameter")
    @Composable
    fun MainScreen(
        mainViewModel: MainViewModel = viewModel(),
        savePDFViewModel: SavePDFViewModel = viewModel()
    ) {
        val navController = rememberAnimatedNavController()
        val navBackStackEntry by navController.currentBackStackEntryAsState()

        if (ActivityCompat.checkSelfPermission(
                this,
                Manifest.permission.MANAGE_EXTERNAL_STORAGE
            ) == PackageManager.PERMISSION_GRANTED
        ) {
            OpenDownloadsFolderAsLaunchedEffect()
        } else {
            RequestPermissions()
        }
        Scaffold(...
But the problem is that of course, when I rotate the device, the activity gets recreated again and if the permissions are already granted the method
OpenDownloadsFolderAsLaunchedEffect()
is called again and that breaks the app
j

Jan Starczewski

08/24/2022, 10:13 AM
@Manuel Lorenzo Hi, can you show the implementation of
OpenDownloadsFolderAsLaunchedEffect
?
m

Manuel Lorenzo

08/24/2022, 10:14 AM
LaunchedEffect(true) {
    mainViewModel.openFolder(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS))
}
@Jan Starczewski
j

Jan Starczewski

08/24/2022, 10:30 AM
oh well sorry
so
m

Manuel Lorenzo

08/24/2022, 10:30 AM
no worries, thanks for the help by the way 🙂
j

Jan Starczewski

08/24/2022, 10:42 AM
@Manuel Lorenzo 1. Long story short effects do not survive configuration changes and after the change they will be relaunched, what you can do to quickly fix the issue is to save state of a
boolean
indicating whether you made a certain job inside effect or not with
rememverSaveable
so It will not be launched on configuration change
val wasLaunched = rememberSaveable { mutableStateOf(false) }
            if (wasLaunched.value.not()) {
                LaunchedEffect(Unit) {
                    // do your calls
                    // mainViewModel.openFolder(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS))
                    // change value
                    wasLaunched.value = true
                }
            }
2. Other solution is to move the state into viewModel assuming it is properly scoped, so I also survives configuration changes, and then you can relay on the value of it to launch an effect or not
m

Manuel Lorenzo

08/24/2022, 10:45 AM
and how can I move it there? I’ve been trying to find out how to handle the permissions there but I can’t find any example
j

Jan Starczewski

08/24/2022, 10:45 AM
What do you mean by “how can I move it there”?
m

Manuel Lorenzo

08/24/2022, 10:45 AM
how to handle the permissions in the view model
j

Jan Starczewski

08/24/2022, 10:46 AM
Do not handle permission in the view model, just keep some kind of information (state) that you have already handled it inside view or not
I can try to provide a full example, but just give me some time
m

Manuel Lorenzo

08/24/2022, 10:47 AM
oh OK thanks!
j

Jan Starczewski

08/24/2022, 11:02 AM
@Manuel Lorenzo but I am wondering, you are asking how to keep the state in ViewModel, or how to handle permissions?
m

Manuel Lorenzo

08/24/2022, 11:05 AM
to be honest, I have a huge mess here, and in particular two problems: • I need to show a list of downloaded PDFs, and I need to request the permissions based on the API level. When the permissions are granted and I get back to my app’s screen, the documents aren’t there so I need to close and open the app again. • When I have my app and I rotate the
OpenDownloadsFolderAsLaunchedEffect
is called again, which kind of restarts the data structure that holds the list of documents of the app, and thus the state isn’t saved
j

Jan Starczewski

08/24/2022, 11:06 AM
Allright, and when you get back to the app it crashes?
m

Manuel Lorenzo

08/24/2022, 11:06 AM
no, it just doesn’t show anything
j

Jan Starczewski

08/24/2022, 11:07 AM
right, so as I understand the problem is only the fact that the folder is being opened twice as you mentioned, yes?
because of the second launch
m

Manuel Lorenzo

08/24/2022, 11:07 AM
yeah, I would consider the second problem the most important one actually
@Composable
    fun MainScreen(
        mainViewModel: MainViewModel = viewModel(),
        savePDFViewModel: SavePDFViewModel = viewModel()
    ) {
        val navController = rememberAnimatedNavController()
        val navBackStackEntry by navController.currentBackStackEntryAsState()

        if (ActivityCompat.checkSelfPermission(
                this,
                Manifest.permission.MANAGE_EXTERNAL_STORAGE
            ) == PackageManager.PERMISSION_GRANTED
        ) {
            OpenDownloadsFolderAsLaunchedEffect()
        } else {
            RequestPermissions()
        }
        Scaffold(...
As you can see here it’s my MainScreen and of course, when rotated the
checkSelfPermission
is called again because the activity was rotated
override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            PSPDFKitAssignmentTheme {
                MainScreen()
            }
        }
    }
Not the best way I guess
j

Jan Starczewski

08/24/2022, 11:10 AM
So as I said, you can prevent the launch with saved value by
rememberSaveable
but to be honest it that case it may not be the best thing to do, because after process death when app is opened again it will prevent downloading the pdfs, I think we should go with viewModel approach
Could you please send me the impl of
openFolder
function ?
m

Manuel Lorenzo

08/24/2022, 11:12 AM
fun openFolder(selectedItem: File) {
        viewModelScope.launch(ioDispatcher) {
            val filesList = getFilesList(selectedItem)
            _documentListLiveData.postValue(buildDocumentListToShow(selectedItem, filesList))
        }
    }
j

Jan Starczewski

08/24/2022, 11:24 AM
I think the easiest solution for you is to check whether your files represented by
_documentListLiveData
is has an empty value before downloading it with
getFilesList
or expose other live data like
canOpenFolder
that can represent the state informing whether you should open the folder or not.
@Composable
fun OpenDownloadsFolderAsLaunchedEffectIfClosed(isClosed: Boolean) {
    if (isClosed) {
        LaunchedEffect(Unit) {
            // do your calls
            // mainViewModel.openFolder(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS))
        }
    }
}
And as the value of
isClosed
use the value stored in your viewModel
@Manuel Lorenzo But it all depends on you what you try to achieve, adding a simple check whether opening a folder is necessary would fix the issue with your crash on configuration change, because the viewModel will survive it and I it wont survive process death, so your folder then will be opened again assuming that’s the behaviour you want
m

Manuel Lorenzo

08/24/2022, 11:48 AM
that’s true indeed
thanks a lot for your help @Jan Starczewski