JetpackCompose #PreviewParameter causes preview to disappear - android

I've been exploring Jetpack Compose lately and discovered this very weird scenario where my #Preview is not showing and Android Studio is literally showing blank empty. No warnings or errors are shown.
This happens when I add #PreviewParameter to my composable function parameter.

The stupid solution was… It turns out that I had my PreviewParameterProvider declared in private and turning it to public or simply removing the visibility modifier fixed it.
class MyProvider : PreviewParameterProvider<MyClass> {
...
}
This wasn't even documented.
I hope Google will make it clear in the documentation or at least give an error in Android Studio so developers won't encounter this frustrating and time-wasting scenario.

I had another issue causing the same behaviour.
blank screen
#Preview
#Composable
fun MyComposablePreview(
#PreviewParameter(MyComposableStateProvider::class) state: MyComposableState
) = MyComposable(state)
works
#Preview
#Composable
fun MyComposablePreview(
#PreviewParameter(MyComposableStateProvider::class) state: MyComposableState
) {
MyComposable(state)
}

I can also add that your implementation of PreviewParameterProvider should not be inner class and should not be inside other class. So
class MyFragment : Fragment() {
class MyProvider : PreviewParameterProvider<MyComposableStateProvider> {
...
}
#Preview
#Composable
fun MyComposablePreview(
#PreviewParameter(MyProvider::class) state: MyComposableState
) {
MyComposable(state)
}
}
won't work either. extract providers to separate files.

Related

Why do I use pure components but still need fragment dependencies?

I have a problem when using compose, then i found the answer
If you use Compose with Fragments, then you may not have the Fragments dependency where viewModels() is defined.
Adding:
implementation "androidx.fragment:fragment-ktx:1.5.2"
use Compose with Fragments, but I use Pure Compose, Also had this problem.
What am I missing? Or is there some connection between fragment and compose?
#AndroidEntryPoint
class MainActivity : ComponentActivity() {
private val userViewModel: UserViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Content(userViewModel)
}
}
}
#Composable
fun Content(userViewModel: UserViewModel) {
val lazyArticleItem = userViewModel.list().collectAsLazyPagingItems()
thread {
repeat(200) {
userViewModel.insert(User())
}
}
LazyColumn(verticalArrangement = Arrangement.spacedBy(16.dp)) {
items(lazyArticleItem) { user ->
Text("user ${user?.id}")
}
}
}
The above is my ui interface code, based on this, I don't think I'm using fragment.
I want to declare my logic. I use Pure Compose instead of Fragment, but actually want to run the code must depend on androidx.fragment:fragment-ktx:1.5.2
It happens because you are using
val userViewModel: UserViewModel by viewModels()
You can access a ViewModel from any composable by calling the viewModel() function.
Use:
val userViewMode : UserViewModel = viewModel()
To use the viewModel() functions, add the androidx.lifecycle:lifecycle-viewmodel-compose:x.x.x
In programming, "fragment" and "compose" can refer to two related concepts:
Fragment: In UI design, a fragment is a portion of an activity's UI, which can be reused in multiple activities or combined to form a single activity. It provides a way to modularize the UI and make it more manageable.
Compose: Compose is a modern UI toolkit for Android app development introduced by Google, which allows developers to build and style UI elements using composable functions. It provides a way to create and reuse UI components that can be combined to form a complete app UI.
Both concepts are aimed at making UI design and development more modular, reusable and maintainable.
Hope this helps!

Jetpack Compose Preview not working when using Koin for Dependency Injection

I want to use Jetpack Compose in my App. I am already using Koin for DI. Because I have a lot of convenience methods in my BaseFragment I want to inherit from it and build the corresponding view with compose.
Now the Problem is that when using DI in the BaseFragment and inheriting from it the preview of the composable wont be shown and following error Message appears:
and following exception is thrown:
java.lang.IllegalStateException: KoinApplication has not been started
at org.koin.core.context.GlobalContext.get(GlobalContext.kt:36)
at org.koin.java.KoinJavaComponent.getKoin(KoinJavaComponent.kt:122)
at org.koin.java.KoinJavaComponent.get(KoinJavaComponent.kt:87)
at org.koin.java.KoinJavaComponent.get$default(KoinJavaComponent.kt:81)
at org.koin.java.KoinJavaComponent.get(KoinJavaComponent.kt)
...
My BaseFragment looks something like this
public abstract class BaseFragment {
private final ActiveViewIdInteractor activeViewIdInteractor =
new ActiveViewIdInteractor(KoinJavaComponent.get(ActiveViewIdService.class));
...
and my Fragment which inherits looks something like this
class ComposeDemoFragment: BaseFragment() {
...
#Composable
fun ComposeDemoFragmentContent() {
Text(text = "Hello World",
Modifier
.fillMaxWidth()
.background(Color.Cyan)
)
}
#Preview
#Composable
private fun Preview() {
ComposeDemoFragmentContent()
}
If using the exact same preview in a Fragment which doesn't inherit from BaseFragment everything works fine. I already included the dependency for "Koin for Compose" and also tried using CoKoin. At this Point I don't know what to do with the error Message or if the error Message is even barely related to the actual Problem.
Is this a Bug or is there a way to bypass this error?
Your #Preview code is being run as-is by Android Studio, looking at your example there is nothing in your ComposeDemoFragmentContent() that is using Koin. However, I'm guessing this is just sample code.
In my app we inject koin components into our main PrimaryTheme{ } which used to break when used with #Preview, we got the same error as you're seeing.
One way around this is to provide a default value to the field being injected, and then put your koin code inside a check for LocalInspectionMode e.g.
val someField = remember { mutableStateOf("Default")}
if (!LocalInspectionMode.current) {
// We're _not_ executing in an Android Studio Preview.
// Use your injected Koin instance here e.g.
val myUseCase: CustomUseCase = get()
someField.value = myUseCase.getSomeValue()
}
Text(
text = someField.value
)
So your previews will use the default value, but your real app will use the koin injected value.
This happens because your #Preview function is inside your Activity. And you probably have an injected member there.
Move it to the root of the file, outside the activity class, and the preview will be rendered without errors.

Scope composables to a parent composable

I'm making an implementation of PreferencesScreen in Compose and I've made all the components like PreferencesSwitch, CheckBox, etc.
Now I'm wondering if there is any way to make it so all the components can only be used inside the scope of the PreferencesScreen function and cannot be used outside of it.
Like for example, in LazyColumn, items can only be used inside LazyColumnScope. I looked at the implementation of it but it used the annotation #LazyScopeMarker so I'm assuming there's different markers for different scopes?
Expected Behaviour:
PreferencesScreen{
PreferencesCheckBox(...){ ... }
}
is possible but,
PreferencesCheckBox(...){ ... }
alone is not possible.
You can declare some scope same like LazyColumn does:
interface PreferencesScreenScope {
#Composable
fun PreferencesCheckBox()
}
private class PreferencesScreenScopeImpl: PreferencesScreenScope {
#Composable
override fun PreferencesCheckBox() {
}
}
interface/class ...Impl is used here to make sure that no other screen can reuse PreferencesScreenScopeImpl, also it adds testing possibility.
Use it in PreferencesScreen:
#Composable
fun PreferencesScreen(content: #Composable PreferencesScreenScope.() -> Unit ) {
PreferencesScreenScopeImpl().content()
}
Use PreferencesScreen like this:
PreferencesScreen {
PreferencesCheckBox()
}

Initiating a ViewModel with Input parameters in Jetpack Compose -> Composable function

I have a ViewModel that takes a string as an argument
class ComplimentIdeasViewModel(ideaCategory : String) : ViewModel() {
//some code here
}
What is the best way to initiate this ViewModel inside a composable fun without using a ViewModel factory and Hilt? A simple statement seems to achieve this inside a composable fun
#Composable
fun SampleComposableFun() {
val compIdeasViewModel = remember { ComplimentIdeasViewModel("someCategory") }
}
There is no warning in Android studio when I try to do this, but this seems too easy to be true, I am able to do this without Dependency Injection and with a ViewModelFactory class. Am I missing something here?
I've tried how you have written yours out and I had issues with screen rotation resetting the view model. I suspect you may too.
I was able to fix it by utilizing the the factory parameter on viewModel() for this, which worked well for me. See this answer on a similar question with example on how to use it: jetpack compose pass parameter to viewModel
This will not provide you the correct instance if viewmodel. See if you store some state in the viewmodel, then using the factory to initialise it is necessary to ensure that you get the same and latest copy of the viewmodel currently present. There is no error since the syntactic implementation is correct. I do not know of any way to do this because most of the times, you don't need to. Why don't you initialise it in the top-level container, like the activity? Then pass it down wherever necessary.
Create a CompositionLocal for your ViewModel.
val YourViewModel = compositionLocalOf { YourViewModel(...) }
Then initialise it (You'd likely use the ViewModelProvider.Factory here). And then provide that to your app.
CompositionLocalProvider(
YourViewModel provides yourInitialisedViewModel,
) {
YourApp()
}
Then reference it in the composable.
#Composable
fun SampleComposableFun(
compIdeasViewModel = YourViewModel.current
) {
...
}
Note, the docs say that ViewModels are not a good fit for CompositionLocals because they will make your composable harder to test, make your composables tied to this app and make it harder to use #Preview.
Some get pretty angry about this. However, if you manage to mock out the ViewModel, so you can test the app and use #Preview and your composables are tied to the app and not generic, then I see no problem.
You can mock a ViewModel fairly simply, providing its dependencies are included as parameters (which is good practice anyway).
open class MockedViewModel : MyViewModel(
app = Application(),
someOtherDependeny = MockedDependecy(),
)
The more dependencies your ViewModel has the more mocking you'll need to do. But I've not found it prohibitive and including the ViewModel as a default parameter has massively sped up development.

Jetpack Compose: Exception while analyzing expression

I am trying to build simple app using Jetpack Compose.
I followed this documentation, downloaded repository and created my own module.
Code is pretty simple:
import android.app.Activity
import android.os.Bundle
import androidx.compose.Composable
import androidx.ui.core.Text
import androidx.ui.core.setContent
import androidx.ui.material.surface.Card
import androidx.ui.graphics.Color
class MainActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApp()
}
}
#Composable
fun MyApp() {
Card(color = Color.Cyan) {
Text("test")
}
}
}
But I noticed that some composable widgets doesnt work and I have following error:
Exception while analyzing expression at (23,9) in
/path/Projects/androidx-master-dev/frameworks/support/ui/compose/src/main/java/app/myown/MainActivity.kt
Where (23,9) references to Card widget
By the way other widgets work, for example I dont have problems with
#Composable
fun MyApp() {
Padding(10.dp) {
Text("test")
}
}
It compiles and runs perfectly.
I got following problem with:
Card
Column
Row
Center
FlexColumn
and I guess many others widgets
I ran into this problem earlier.
There is an implicit need to have import androidx.compose.composer in every Kotlin source file that has #Composable functions. I say "implicit" because Android Studio thinks that it is unnecessary and has a tendency to remove that line (e.g., you ask it to optimize imports). Some #Composable functions can survive without this import, but others cannot.
As I understand it, this is one of those things that will get better as the libraries and tooling evolve, but at the moment, just keep an eye out for that import and add it if it is missing and you are getting weirder-than-normal results.

Categories

Resources