I have defined the Kotlin reflection dependency in my Gradle file as follows:
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
Within the App, reflection works just fine. However, when I use reflection within a Composable preview:
#Composable
#Preview
fun MyComposablePreviewUsingReflection() {
val foo = SomeClass::class.sealedSubclasses.size
}
I get the kotlin.jvm.KotlinReflectionNotSupportedError. When I build the App and use reflection in a normal Composable (i.e., not a preview), reflection works just fine.
Is there a way to get reflection working in a Compose preview?
Related
I'm just trying to get started with Android development and Kotlin using Jetpack Compose. Note that, I'm a Kotlin novice, so I'm trying to learn along the way. I come from JavaScript/TypeScript background, so I'm trying to learn by thinking in JavaScript terms and implement in Kotlin terms by searching online.
I'm trying to list all installed applications on the device. While the app was working as expected up till now, I needed to add a feature to sort the installed app names. I referred to: https://www.bezkoder.com/kotlin-sort-list-objects/#Create_Class_for_handling_sorting. As soon as I added a custom class to sort the List<ApplicationInfo>, my app stopped building.
I have included my repo here: https://github.com/Hrishikesh-K/TryKotlin
If I comment these lines and this line as well, the app builds fine. With the current setup, I get an error:
Functions which invoke #Composable functions must be marked with the #Composable annotation
which points to line 21, character 18, which is the start of the word compare.
I don't understand why Compose would care about a custom class, it's not a Composable function after all. What am I missing?
In the compare method you are using LocalContext.current
override fun compare(o1 : ApplicationInfo, o2 : ApplicationInfo): Int {
return o1.loadLabel(LocalContext.current.packageManager).toString().compareTo(o2.loadLabel(LocalContext.current.packageManager).toString())
}
You can't use a #Composable functions if the method is not marked with the #Composable.
Use something different like:
data class CompareApplicationNames(val context: Context) : Comparator<ApplicationInfo> {
override fun compare(o1 : ApplicationInfo, o2 : ApplicationInfo): Int {
return o1.loadLabel(context.packageManager).toString().compareTo(o2.loadLabel(context.packageManager).toString())
}
}
Then just use:
Log.d("sorted:", listOfApplications.sortedWith(CompareApplicationNames(LocalContext.current)).toString())
When we run Jacoco code coverage of Jetpack Compose function, I like to exclude all Preview functions.
I can do so using
#Retention(AnnotationRetention.RUNTIME)
#Target(AnnotationTarget.FUNCTION)
internal annotation class ExcludeFromJacocoGeneratedReport
Then for the function I like to exclude from the report, I annotate it with
#ExcludeFromJacocoGeneratedReport
#Preview(
name = "Name"
)
#Composable
private fun MyComposePreview() {
// ... function content
}
This works. But I have to add the annotation to all my Preview functions.
I am hoping I can do it in the build.gradle file to exclude them all.
Is there a way to do so?
The documentation for Preview says that it can be applied to:
Annotation classes, that could then be used to annotate #Composable methods or other annotation classes, which will then be considered as indirectly annotated with that Preview.
So I suppose you could create an annotation "PreviewExludedFromJacoco" that you annotate with Preview. This is not exactly what you ask for, but it would allow you to have only one annotation for each Preview function.
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.
I've begun implementing a feature on my application with Jetpack Compose.
The application uses Swig to generate code that allows me to interact with a C++ layer from Kotlin/Java. In order to use the C++ layer, I load the library like this in the app's main activity:
static {
System.loadLibrary("{swig library name}");
}
When I attempt to use Jetpack Compose previews, I get the following render problem:
java.lang.UnsatisfiedLinkError: 'void {package containing swig's generated code}.swigWrappersJNI.swig_module_init()'
The function does use a class generated by Swig, so I'm assuming that's why this is happening.
I tried getting around this by adding the following to the preview function:
System.loadLibrary("{swig library name}")
but that just results in the following render problem:
java.lang.UnsatisfiedLinkError: no {swig library name} in java.library.path:
Does anybody know how/if I can properly link the library so that I can use Compose's preview feature as I work on this app?
Edit:
For a little more info, I've tried the following methods for loading my library:
#Composable
#Preview
fun ___Preview() {
System.loadLibrary("{swig library name}");
// Actual preview code here
}
object LibraryLoader {
fun load() {
System.loadLibrary("{swig library name}")
}
}
#Composable
#Preview
fun ___Preview() {
LibraryLoader.load()
// Actual preview code here
}
(This one was a bit of a shot in the dark. I thought the key difference could've been loading it within a static block, but this had no effect.)
The previews can be successfully deployed to a device/emulator.
I think the issue here is that the library lives in lib/{abi}/{swig library name}.so, so it exists when running on a device/emulator (with a supported ABI) but not for Compose previews.
What I've done to work around it is create a copy of my model classes that don't use any of Swig's generated code so I can take advantage of previews when creating the layout, then replacing them with the actual model classes for testing functionality. Definitely not a solution I'm particularly happy with but it may be the only way to use Compose previews in this type of situation.
so after jetpack compose beta was released, I updated my Android Studio Canary to the latest version, but I can't preview my app, even when I annotate my code with #Preview, there is no button called show preview or show code, they just disappeared.
Your UI code needs to be inside a compose fuction.
#Composable
fun Greeting(name: String) {
Text (text = "Hello $name!")
}
The composable function must not take any parameters. For this reason, you can't preview the Greeting() function directly. Instead, make a second function named PreviewGreeting(), which calls Greeting() with an appropriate parameter. Add the #Preview annotation before #Composable.
#Preview
#Composable
fun PreviewGreeting() {
Greeting("Android")
}
after 1 day I realized that I had to download Android Studio Canary from the website, and all thing worked just fine with the new compose beta.
Download the 'Android studio Arctic Fox' from this link