#file:JvmName vs #JvmStatic in companion object - android

I'm migrating part of application from Java to Kotlin and have a question about that.
What is preferable or better way ?
File with annonation #file:JvmName and funtion inside
#file:JvmName("ClassX")
fun funX() {}
or
Typical class with #JvmStatic annotation inside companion object
class ClassX {
companion object {
#JvmStatic
fun funX() {}
}
}

Let's look at the decompiled code to answer this question.
Kotlin file with a #JvmName annotation like yours:
#file:JvmName("ClassX")
fun funX() {}
will be compiled into a bytecode, analogous to this Java code:
#JvmName(
name = "ClassX"
)
public final class ClassX {
public static final void funX() {
}
}
Pretty similar to what you'd probably write when using Java, right?
A Kotlin class with a companion object like this:
class ClassX {
companion object {
#JvmStatic
fun funX() {}
}
}
is analogous to this Java code:
public final class ClassX {
public static final ClassX.Companion Companion = new ClassX.Companion((DefaultConstructorMarker)null);
#JvmStatic
public static final void funX() {
Companion.funX();
}
public static final class Companion {
#JvmStatic
public final void funX() {
}
private Companion() {
}
// $FF: synthetic method
public Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
}
As you see, class with a companion object will generate more code.
Is it good or bad? You decide. I'd say it's bad and it's better to use a file with #JvmName annotation. There is also a discussion on Kotlin's forum about this topic: read the best practices.
BTW, you can look at the bytecode and decompiled Java code in IntelliJ IDEA via "Tools" -> "Kotlin" -> "Show Kotlin Bytecode".

Related

Define common functions in kotlin

For define Util(common functions) we can use 3 approach :
First :
We can define a File and add function to it like this :
fun func1(): String {
return "x"
}
fun func2(): String {
return "y"
}
Then we can use this in every where like this :
fileName.func1()
Second :
We can define a class and add these functions to class like this :
class Util{
fun func1(): String {
return "x"
}
fun func2(): String {
return "y"
}
}
we can inject it and use this like :
private val mUtil: Util by inject()
mutil.func1()
Third:
We can define an object like this :
object Util{
fun func1(): String {
return "x"
}
fun func2(): String {
return "y"
}
Then we can use this like :
Util.func1()
suppose that we have reusable fun that it use in different class , activity or fragment now is better to use first , second or third ?
According to my understanding :
In the first example, we simply define a function to be used within the scope of component (could be a fragment or an activity ..)
In the second example, which I think more used in Java than Kotlin, you simply create a class in which you define some static variable or function to be accessed in your components later
Example 3, I have no idea as I never used that
first one is static function
second one is normal function
third is singleton function
you can decomplie to see the java code and see their differs.
Kotlin file
fun fun1() {}
class TestClass {
fun testFun() {}
}
object ObjectClass {
fun objectFun() {}
}
Java file
public final class TTTKt {
public static final void fun1() {
}
}
public final class TestClass {
public final void testFun() {
}
}
public final class ObjectClass {
public static final ObjectClass INSTANCE;
public final void objectFun() {
}
private ObjectClass() {
}
static {
ObjectClass var0 = new ObjectClass();
INSTANCE = var0;
}
}

Can I use lambda if the abstract class has more than 1 method in Kotlin?

For example if I this piece of code compiler is suggesting to change to
private val viewListener = object : View.OnClickListener {
override fun onClick(v: View) {
toast("View clicked!$v")
}
}
following code
private val viewListener = View.OnClickListener { v ->
toast("View clicked!$v")
}
but if I have this abstract class
public abstract class DrawableCallback implements Callback {
public void onLoaded() {}
public void onError() {}
public static int extractDrawable() {}
public abstract void onDrawableExtracted(int color);
}
and I use it here
private val drawableCallback = object : DrawableCallback() {
override fun onDrawableExtracted(color: Int) {
//...
}
}
Here it seems I can't convert to lambda , can you explain why?
You cannot use it with abstract classes. Refer to Kotlin Lang documentation (also in comments by Michael). It states clearly:
Note that SAM conversions only work for interfaces, not for abstract
classes, even if those also have just a single abstract method.
. If you need to know more (like in why not?), refer to this discussion Why can't #FunctionalInterface be applied to a SAM abstract base class. This discussion is around Java, but equally applies to Kotlin on JVM for obvious reasons.

Access Parent class variables in companion Object in Kotlin

I am trying to call static function of one class in other like java , But in kotlin I can not make a static function , and I have to make a companion object in which I have to define my function , But while doing this I am not able to access parent class variables , is there any way I can achieve this in kotlin .
class One {
val abcList = ArrayList<String>()
companion object {
fun returnString() {
println(abcList[0]) // not able to access abcList here
}
}
}
class Two {
fun tryPrint() {
One.returnString()
}
}
// In Java we can do it like this
class One {
private static ArrayList<String> abcList = new ArrayList<>();
public void tryPrint() {
// assume list is not empty
for(String ab : abcList) {
System.out.println(ab);
}
}
public static void printOnDemand() {
System.out.println(abcList.get(0));
}
}
class Two {
public void tryPrint(){
One.printOnDemand();
}
}
I want to access fun returnString() like static function of class one like we do in java , if any one have achieved this please help .
In your case abcList is a member variable of the class. Each instance of a class has their own version of its member variables which means that a static method cannot access them. If you want to access it from your companion object it has to be static too.
class One {
companion object {
val abcList = ArrayList<String>()
fun returnString() {
println(abcList[0])
}
}
}
class Two {
fun tryPrint() {
One.returnString()
}
}
This code will work, but keep in mind that in this case there will be only one instance of abcList. Accessing a member variable from a static function is not possible (not even in Java).
Here's the Kotlin version of your Java example:
class One {
companion object {
val abcList = ArrayList<String>()
fun printOnDemand() {
println(abcList[0])
}
}
fun tryPrint() {
for (ab in abcList) {
println(ab)
}
}
}
class Two {
fun tryPrint() {
One.printOnDemand()
}
}
Rule: You can't access static properties, members of a class in none static members and you can't access none static properties, members of a class in static members which is the companion object class.
This rule is in both Java and Kotlin. If you want to access a none static member of a class
inside static members you have to declare it inside companion object class.
Use the following code for you case.
object One {
val abcList: MutableList<String> = mutableListOf()
fun returnString() {
println(abcList[0])
}
fun printOnDemand() {
println(abcList[0]);
}
}
class Two {
fun tryPrint() {
One.printOnDemand()
}
}

How to use Companion object in the main class

in the below 2 posted examples, i am trying to convert the java code to kotlin code.
the kotlin code does not work and IntelliJ says that I should use companion object.
please let me know how to correct this error.
code_kotlin*
#SpringBootApplication
class MyApplication {
}
fun main(args: Array<String>) {
SpringApplication.run(MyApplication::class.java, *args)
}
codeJava
#SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
Include main function inside Application class, as Java code does:
#SpringBootApplication
class MyApplication {
fun main(args: Array<String>) {
SpringApplication.run(MyApplication::class.java, *args)
}
}
Unlike Java or C#, Kotlin doesn’t have static members or member functions. Kotlin recommends to simply use package-level functions instead. For more detail you can get help from link. You can call companion object like-
class YourClassName{
companion object {
//write what you want to make static
}
}

How should I get Resources(R.string) in viewModel in Android (MVVM and databinding)

I am currently using databinding and MVVM architecture for android. What would be the best way to get string resources in ViewModel.
I am not using the new AndroidViewModel component, eventbus or RxJava
I was going through the aproach of interfaces where Activity will be responsible for providing resources. But recently I found a similar question with this answer where a single class using application context is providing all resources.
Which would be the better approach? or is there something else that I can try?
You can access the context by implementing AndroidViewModel instead of ViewModel.
class MainViewModel(application: Application) : AndroidViewModel(application) {
fun getSomeString(): String? {
return getApplication<Application>().resources.getString(R.string.some_string)
}
}
You can also use the Resource Id and ObservableInt to make this work.
ViewModel:
val contentString = ObservableInt()
contentString.set(R.string.YOUR_STRING)
And then your view can get the text like this:
android:text="#{viewModel.contentString}"
This way you can keep the context out of your ViewModel
Not at all.
Resource string manipulation belongs the View layer, not ViewModel layer.
ViewModel layer should be free from dependencies to both Context and resources. Define a data type (a class or enum) that ViewModel will emit. DataBinding has access to both Context and resources and can resolve it there. Either via #BindingAdapter (if you want the clean look) or a plain static method (if you want flexibility and verbosity) that takes the enum and Context and returns String : android:text="#{MyStaticConverter.someEnumToString(viewModel.someEnum, context)}". (context is synthetic param in every binding expression)
But in most cases, String.format is enough to combine resource string format with data provided by ViewModel.
It may seem like "too much code in XML", but XML and bindings are the View layer. The only places for view logic, if you discard god-objects: Activities and Fragments.
//edit - more detailed example (kotlin):
object MyStaticConverter {
#JvmStatic
fun someEnumToString(type: MyEnum?, context: Context): String? {
return when (type) {
null -> null
MyEnum.EENY -> context.getString(R.string.some_label_eeny)
MyEnum.MEENY -> context.getString(R.string.some_label_meeny)
MyEnum.MINY -> context.getString(R.string.some_label_miny)
MyEnum.MOE -> context.getString(R.string.some_label_moe)
}
}
}
usage in XML:
<data>
<import type="com.example.MyStaticConverter" />
</data>
...
<TextView
android:text="#{MyStaticConverter.someEnumToString(viewModel.someEnum, context)}".
For more complicated cases (like mixing resource labels with texts from API) instead of enum use sealed class that will carry the dynamic String from ViewModel to the converter that will do the combining.
For simplest cases, like above, there is no need to invoke Context explicitly at all. The built-in adapter already interprets binding int to text as string resource id. The tiny inconvenience is that when invoked with null the converter still must return a valid ID, so you need to define some kind of placeholder like <string name="empty" translatable="false"/>.
#StringRes
fun someEnumToString(type: MyEnum?): Int {
return when (type) {
MyEnum.EENY -> R.string.some_label_eeny
MyEnum.MEENY -> R.string.some_label_meeny
MyEnum.MINY -> R.string.some_label_miny
MyEnum.MOE -> R.string.some_label_moe
null -> R.string.empty
}
}
Quick-and-dirty hack would be to emit a #StringRes Int directly, but that makes ViewModel dependent on resources.
"Converters" (a collection of unrelated, static and stateless functions) is a pattern that I use a lot. It allows to keep all the Android's View-related types away from ViewModel and reuse of small, repetitive parts across entire app (like converting bool or various states to VISIBILITY or formatting numbers, dates, distances, percentages, etc). That removes the need of many overlapping #BindingAdapters and IMHO increases readability of the XML-code.
an updated version of Bozbi's answer using Hilt
ViewModel.kt
#HiltViewModel
class MyViewModel #Inject constructor(
private val resourcesProvider: ResourcesProvider
) : ViewModel() {
...
fun foo() {
val helloWorld: String = resourcesProvider.getString(R.string.hello_world)
}
...
}
ResourcesProvider.kt
#Singleton
class ResourcesProvider #Inject constructor(
#ApplicationContext private val context: Context
) {
fun getString(#StringRes stringResId: Int): String {
return context.getString(stringResId)
}
}
Just create a ResourceProvider class that fetch resources using Application context. In your ViewModelFactory instantiate the resource provider using App context. You're Viewmodel is Context free and can be easily testable by mocking the ResourceProvider.
Application
public class App extends Application {
private static Application sApplication;
#Override
public void onCreate() {
super.onCreate();
sApplication = this;
}
public static Application getApplication() {
return sApplication;
}
ResourcesProvider
public class ResourcesProvider {
private Context mContext;
public ResourcesProvider(Context context){
mContext = context;
}
public String getString(){
return mContext.getString(R.string.some_string);
}
ViewModel
public class MyViewModel extends ViewModel {
private ResourcesProvider mResourcesProvider;
public MyViewModel(ResourcesProvider resourcesProvider){
mResourcesProvider = resourcesProvider;
}
public String doSomething (){
return mResourcesProvider.getString();
}
ViewModelFactory
public class ViewModelFactory implements ViewModelProvider.Factory {
private static ViewModelFactory sFactory;
private ViewModelFactory() {
}
public static ViewModelFactory getInstance() {
if (sFactory == null) {
synchronized (ViewModelFactory.class) {
if (sFactory == null) {
sFactory = new ViewModelFactory();
}
}
}
return sFactory;
}
#SuppressWarnings("unchecked")
#NonNull
#Override
public <T extends ViewModel> T create(#NonNull Class<T> modelClass) {
if (modelClass.isAssignableFrom(MainActivityViewModel.class)) {
return (T) new MainActivityViewModel(
new ResourcesProvider(App.getApplication())
);
}
throw new IllegalArgumentException("Unknown ViewModel class");
}
}
You can use the Resource Id to make this work.
ViewModel
val messageLiveData= MutableLiveData<Any>()
messageLiveData.value = "your text ..."
or
messageLiveData.value = R.string.text
And then use it in fragment or activity like this:
messageLiveData.observe(this, Observer {
when (it) {
is Int -> {
Toast.makeText(context, getString(it), Toast.LENGTH_LONG).show()
}
is String -> {
Toast.makeText(context, it, Toast.LENGTH_LONG).show()
}
}
}
Ideally Data Binding should be used with which this problem can easily be solved by resolving the string inside the xml file. But implementing data binding in an existing project can be too much.
For a case like this I created the following class. It covers all cases of strings with or without arguments and it does NOT require for the viewModel to extend AndroidViewModel and this way also covers the event of Locale change.
class ViewModelString private constructor(private val string: String?,
#StringRes private val stringResId: Int = 0,
private val args: ArrayList<Any>?){
//simple string constructor
constructor(string: String): this(string, 0, null)
//convenience constructor for most common cases with one string or int var arg
constructor(#StringRes stringResId: Int, stringVar: String): this(null, stringResId, arrayListOf(stringVar))
constructor(#StringRes stringResId: Int, intVar: Int): this(null, stringResId, arrayListOf(intVar))
//constructor for multiple var args
constructor(#StringRes stringResId: Int, args: ArrayList<Any>): this(null, stringResId, args)
fun resolve(context: Context): String {
return when {
string != null -> string
args != null -> return context.getString(stringResId, *args.toArray())
else -> context.getString(stringResId)
}
}
}
USAGE
for example we have this resource string with two arguments
<string name="resource_with_args">value 1: %d and value 2: %s </string>
In ViewModel class:
myViewModelString.value = ViewModelString(R.string.resource_with_args, arrayListOf(val1, val2))
In Fragment class (or anywhere with available context)
textView.text = viewModel.myViewModelString.value?.resolve(context)
Keep in mind that the * on *args.toArray() is not a typing mistake so do not remove it. It is syntax that denotes the array as Object...objects which is used by Android internaly instead of Objects[] objects which would cause a crash.
I don't use data bindig but I guess you can add an adapter for my solution.
I keep resource ids in the view model
class ExampleViewModel: ViewModel(){
val text = MutableLiveData<NativeText>(NativeText.Resource(R.String.example_hi))
}
and get text on a view layer.
viewModel.text.observe(this) { text
textView.text = text.toCharSequence(this)
}
You can read more about native text in the article
For old code which you don't want to refactor you can create an ad-hoc class as such
private typealias ResCompat = AppCompatResources
#Singleton
class ResourcesDelegate #Inject constructor(
#ApplicationContext private val context: Context,
) {
private val i18nContext: Context
get() = LocaleSetter.createContextAndSetDefaultLocale(context)
fun string(#StringRes resId: Int): String = i18nContext.getString(resId)
fun drawable(#DrawableRes resId: Int): Drawable? = ResCompat.getDrawable(i18nContext, resId)
}
and then use it inside your AndroidViewModel.
#HiltViewModel
class MyViewModel #Inject constructor(
private val resourcesDelegate: ResourcesDelegate
) : AndroidViewModel() {
fun foo() {
val helloWorld: String = resourcesDelegate.string(R.string.hello_world)
}
If you are using Dagger Hilt then #ApplicationContext context: Context in your viewModel constructor will work. Hilt can automatically inject application context with this annotation. If you are using dagger then you should provide context through module class and then inject in viewModel constructor. Finally using that context you can access the string resources. like context.getString(R.strings.name)
You should use something like "UIText" sealed class to mange it all over your project(as Philip Lackner have done).
sealed class UIText {
data class DynamicString(val value:String):UIText()
class StringResource(
#StringRes val resId: Int,
vararg val args: Any
):UIText()
#Composable
fun asString():String{
return when(this){
is DynamicString -> value
is StringResource -> stringResource(resId, *args)
}
}
}
Then everyWhere in your project in place of String, use UIText.StringResource easily
Still don't find here this simple solution:
android:text="#{viewModel.location == null ? #string/unknown : viewModel.location}"

Categories

Resources