With the release of the Kotlin RC, I started writing an app to learn it however I can not figure out how to get Parcelable to work.
the data class:
data class Project (val reponame:String,
val username:String,
val language:String,
val vcsUrl:String,
val branches:Map<String, Branch>) : Parcelable {
companion object {
val CREATOR = object : Parcelable.Creator<Project> {
override fun createFromParcel(`in`: Parcel): Project {
return Project(`in`)
}
override fun newArray(size: Int): Array<Project?> {
return arrayOfNulls(size)
}
}
}
protected constructor(parcelIn: Parcel) : this (
parcelIn.readString(),
parcelIn.readString(),
parcelIn.readString(),
parcelIn.readString(),
mapOf<String, Branch>().apply {
parcelIn.readMap(this, Branch::class.java.classLoader)
}
)
override fun describeContents(): Int {
throw UnsupportedOperationException()
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeString(reponame)
dest.writeString(username)
dest.writeString(language)
dest.writeString(vcsUrl)
dest.writeMap(branches)
}
}
Reading it:
class ProjectDetailActivity : BaseActivity() {
lateinit var project: Project
companion object {
const val EXTRA_PROJECT = "extra_project"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
project = intent.extras.getParcelable(EXTRA_PROJECT)
tvTitle.text = project.reponame
}
}
The exception:
Caused by: android.os.BadParcelableException: Parcelable protocol requires a Parcelable.Creator object called CREATOR on class com.eggman.circleciandroid.model.Project
at android.os.Parcel.readParcelableCreator(Parcel.java:2415)
at android.os.Parcel.readParcelable(Parcel.java:2337)
at android.os.Parcel.readValue(Parcel.java:2243)
at android.os.Parcel.readArrayMapInternal(Parcel.java:2592)
at android.os.BaseBundle.unparcel(BaseBundle.java:221)
at android.os.BaseBundle.get(BaseBundle.java:281)
at com.eggman.circleciandroid.ui.ProjectDetailActivity.onCreate(ProjectDetailActivity.kt:22)
I am sure it is something simple I am missing, has anyone else had success with Parcelable on latest Kotlin?
Kotlin Version: 1.0.0-rc-1036
Kotlin Plugin Version: 1.0.0-rc-1036-IJ143-4
Code is viewable # https://github.com/eggman87/circle-kotlin
Kotlin RC dropped previously deprecated generation of static fields for all companion object properties (learn more in this answer).
Now only those marked by const, lateinit or #JvmField will have a static field generated.
You need to annotate val CREATOR by #JvmField annotation since Android Framework expects a static field CREATOR in your class.
Here you have some useful Kotlin extension functions that will help you to create your CREATORs and also some examples (using data classes and list inside the data class)
Gist: Data Class & Parcelables example
I'm using this code in an Android App: (link)
The same code you can find it here: (link)
Related
I have the following class:
#Parcelize
data class Collection<T : Parcelable> constructor(
var models: List<T>,
var cursor: String?
) : Parcelable
When I was using Kotlin 1.4.10, the project builds correctly without any errors, then I updated the project to Kotlin 1.4.21, and migrated to use kotlin-parcelize instead of kotlin-android-extensions, so after Kotlin update, when building the project I get the following errors:
> Task :domain:kaptDebugKotlin FAILED
/Library/DevelopmentArea/workspace/baaz_android_new/clean_domain/domain/build/tmp/kapt3/stubs/debug/com/myapp/domain/model/Collection.java:101: error: non-static type variable T cannot be referenced from a static context
public final com.myapp.domain.model.Collection<T>[] newArray(int size) {
^/Library/DevelopmentArea/workspace/baaz_android_new/clean_domain/domain/build/tmp/kapt3/stubs/debug/com/myapp/domain/model/Collection.java:110: error: non-static type variable T cannot be referenced from a static context
public final com.myapp.domain.model.Collection<T> createFromParcel(#org.jetbrains.annotations.NotNull()
Note: I'm using Android Studio 4.1.1
Currently to fix the errors that I get, and meanwhile, keep using Kotlin 1.4.21, I just removed the #Parcelize annotation from any class with a generic type usage like the one in the question, and just implemented the Parcelable with the old way like the following:
data class Collection<T> constructor(
var models: List<T>,
var cursor: String?
) : Parcelable {
constructor(parcel: Parcel) : this(
mutableListOf<T>().also { list: List<T> ->
parcel.readList(list, Collection<T>::models.javaClass.classLoader)
},
parcel.readString())
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeList(models)
parcel.writeString(cursor)
}
override fun describeContents(): Int = 0
companion object {
#JvmField
val CREATOR = object : Parcelable.Creator<Collection<Parcelable>> {
override fun createFromParcel(source: Parcel): Collection<Parcelable> {
return Collection(source)
}
override fun newArray(size: Int): Array<Collection<Parcelable>?> {
return arrayOfNulls(size)
}
}
}
}
Recently I have started to learn kodein dependency injection here I'm trying to load data into ArrayList and then inject that ArrayList into desired service or activity however I'm unable to do this and facing run time crashes any guidance will be appreciated.!
My Service Class:
class NotificationAccessibilityService() : AccessibilityService(),KodeinAware {
override val kodein: Kodein = Kodein.lazy{
import(AppModules().appModule(applicationContext))
}
val logger by instance<Logger>()
fun insertdata(){
//trying to insert data into arraylist
logger.list = ArrayList()
logger.list.add(mNodeInfo)
}
}
AppModules Class:
class AppModules() {
fun appModule(context:Context) = Kodein.Module{
bind<Logger>() with singleton { AndroidLogger() }
}
}
Logger interface:
interface Logger {
var list:ArrayList<AccessibilityNodeInfo>
}
Logger Class:
class AndroidLogger() : Logger {
override var list: ArrayList<AccessibilityNodeInfo>
get() = TODO("not implemented")
set(value) {}
}
In my Activity I'm extending kodeinaware and then trying to access that ArrayList from kodein however app is crashing:
class NormalCopy : AppCompatActivity(),KodeinAware {
override val kodein: Kodein by closestKodein()
val logger by kodein.instance<Logger>()
var accesslist = logger.list
}
Sorry, maybe I misunderstood you but it looks like you have to just remove TODO from your Logger implementation
class AndroidLogger() : Logger {
override var list: ArrayList<AccessibilityNodeInfo> = arrayListOf()
}
My Parcelable class wants me to include a CREATOR field, but I have no idea what such a field looks like. I have silenced the complaint with SupressLint in the meanwhile but would like to have that CREATOR field. Any idea what it looks like?
#SuppressLint("ParcelCreator")
#Parcelize
class RecipeTemplate: Parcelable {
var recipeHeader: String? = null
var recipeText: String? = null
var recipeImage: String? = null
var recipeKey: String? = null
}
As Joao Alves says:
There’s still one small problem with Parcelize though. At the moment
there’s an issue with Android Studio showing an error about an
incomplete implementation of the Parcelable interface:
This is a known bug in the IDE itself and you can ignore it, there’s
nothing wrong with the code and it works as expected. You can keep
track of the issue here. At the moment it’s In Progress state.
So you can just ignore this warning.
A sample parcelable implementation would look like this:
data class WarriorParcelable(val name : String,
val weapon : String) : Parcelable {
companion object CREATOR : Parcelable.Creator<WarriorParcelable> {
override fun createFromParcel(parcel: Parcel): WarriorParcelable = WarriorParcelable(parcel)
override fun newArray(size: Int): Array<WarriorParcelable?> = arrayOfNulls(size)
}
private constructor(parcel: Parcel) : this (parcel.readString(),parcel.readString())
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeString(name)
dest.writeString(weapon)
}
override fun describeContents(): Int = 0
}
However, if you are using the #Parcelize, you don't need to write the implementation codes for the Parcelable. It will be implemented for you.
#Parcelize is still an experimental feature, upgrade to the latest kotlin plugin version and add this code to your build.grade file
android {
...
androidExtensions {
experimental = true
}
}
The IDE will stop complaining. As you can see below; I modified your class a bit but it's essentially this same thing.
#Parcelize
data class RecipeTemplate(var recipeHeader: String? = null,
var recipeText: String? = null,
var recipeImage: String? = null,
var recipeKey: String? = null) : Parcelable
I got some issues with Kotlin when translating my android project from java to Kotlin.
Say i have interface I and interface O which extends interface I.
interface I{
}
interface O: I{
}
And generic class A which have generic parameter V that extends interfaceI, and generic class B which extends class A:
abstract class A<V: I> {
}
class B : A<O>() {
}
When i'm trying to create such property:
val returnB: A<I>
get() = b
I'm getting compiler error 'required A, found B'. In Java this will work without any issues. How can i access this using Kotlin ?
I need to use this approach for Basic classes in my application.
BaseViewModel which have generic parameter for Navigator class:
abstract class BaseViewModel<N>(application: Application, val repositoryProvider:
RepositoryProvider) : AndroidViewModel(application) {
var navigator: N? = null
fun onDestroyView() {
navigator = null
}
open fun onViewAttached() {
}
}
BaseActivity class:
abstract class BaseActivity<T : ViewDataBinding, V : BaseViewModel<BaseNavigator>> : AppCompatActivity(),
BaseFragment.Callback, BaseNavigator {
// .......
private var mViewModel: V? = null
/**
* Override for set view model
* #return view model instance
*/
abstract val viewModel: V
// .......
}
BaseNavigator interface uses for VM - View communication:
interface BaseNavigator {
fun invokeIntent(intent: Intent?, b: Bundle?, c: Class<*>?,
forResult: Boolean, requestCode: Int)
fun replaceFragment(fragment: Fragment, addToBackStack: Boolean)
fun showDialogFragment(fragment: DialogFragment?, tag: String?)
fun showToast(message: String?)
}
Here example code where i'm extending these classes:
AuthViewModel:
class AuthViewModel(context: Application, repositoryProvider: RepositoryProvider) :
BaseViewModel<AuthNavigator>(context,repositoryProvider) {
// ....
}
AuthNavigator:
interface AuthNavigator : BaseNavigator {
fun requestGoogleAuth(code: Int)
fun requestFacebookAuth(callback: FacebookCallback<LoginResult>)
}
And AuthActivity class where error was appeared:
class AuthActivity : BaseActivity<ActivityAuthBinding, BaseViewModel<BaseNavagator>>(),
GoogleApiClient.OnConnectionFailedListener, AuthNavigator {
#Inject
lateinit var mViewModel: AuthViewModel
override val viewModel: BaseViewModel<BaseNavigator>
get() = mViewModel // Required:BaseViewModel<BaseNavigator> Found: AuthViewModel
}
I'm also tried to change generic parameter in AuthActivity from BaseViewModel to AuthViewModel, but compiler throws error 'required BaseViewModel'.
And i tried to change
override val viewModel: BaseViewModel<BaseNavigator>
get() = mViewModel
to
override val viewModel: AuthViewModel
get() = mViewModel
but in this case compiler throws error 'Property type is 'AuthViewModel', which is not a subtype type of overridden'.
update:
That works when i add out property to BaseViewModel:
BaseViewModel<out N : BaseNavigator>
But in this case i can only create
private var navigator: N? = null
which i need to be public so i can set it in the Activity class. Can i create public setter for this property? When i'm trying to create setter an error occurs:
private var navigator: N? = null
fun setNavigator(n: N) { // error: Type parameter N is declared as 'out' but occurs in 'in' position in type N
navigator = n
}
It looks like you are expecting the type parameter to behave covariantly. Kotlin uses declaration-site variance. If you do not specify the variance, generic type parameters are invariant.
In other words, right now there is no relationship between A<I> and A<O>. But if you declare
abstract class A<out V : I>
then A<O> is a subtype of A<I>.
(There is also <in> for contravariance, which works the other way around. See https://kotlinlang.org/docs/reference/generics.html for more details.)
I am trying to build a set of providers for realm objects.
Here is an example structure I've tried to build:
Interface:
interface IDataProvider<out T : RealmObject> {
fun getRealmObject(): T
}
Base provider class with companion function for typed provider instantiation:
open abstract class BaseProvider<out T : RealmObject> constructor(protected val context: Context?) : IDataProvider<T> {
companion object {
fun <T : RealmObject, E : BaseProvider<T>> create(context: Context?): E {
if (something) {
return SomeChildProviderProvider(context)
} else {
throw TypeNotSupportedException()
}
}
}
}
And here is a child class:
class SomeChildProvider(context: Context?) : BaseProvider<ChildRealmModel>(context){
override fun getRealmObject(): ChildRealmModel {
throw UnsupportedOperationException("not implemented")
}
}
Problem I have is on the line
return SomeChildProviderProvider(context)
Type mismatch.
Required: E.
Found: SomeChildProvider.
I can't figure out why it does not see that E is actually SomeChildProvider.
Thank you.
P.S. I know that I can cast it to E, but in my opinion, it should not be needed in this situation. Maybe I am missing something obvious here or probably lack of Kotlin knowledge.
UPDATE1:
After the first answer, we have realized that code above does not make much sense since we have to define a type of returning provider and to pass it into create method. Initial idea was that create method returns some type which is BaseProvider subtype. Here are the changes I have made in order to support the initial idea:
IDataProvider
interface IDataProvider {
fun execute(realm: Realm)
fun createModel(realm: Realm): RealmObject
}
BaseProvider
open abstract class BaseProvider constructor(protected val context: Context?) : IDataProvider {
override fun execute(realm: Realm) {
realm.executeTransaction { r ->
createModel(r)
}
}
companion object {
fun create(context: Context?): IDataProvider {
if (something) {
return ChildProvider(context)
} else {
throw TypeNotSupportedException()
}
}
}
}
ChildProvider
class ChildProvider(context: Context?) : BaseProvider(context) {
override fun createModel(realm: Realm): ChildRealmModel {
var realmObject = realm.createObject(ChildRealmModel ::class.java)
//object property initialization
return realmObject
}
}
UI call
BaseProvider.create(context).execute(realm)
Although, createModel method returns RealmObject, it's instance will be of ChildRealmModel. What I don't like about it is that we have to inspect instance type and cast into if we need exact model somewhere else.
Your code is not consistent.
In the function declaration you pledge to return E, which is a subtype of BaseProvider<T> and can be chosen by the user on the call site.
But in the implementation you return SomeChildProviderProvider, which is of course a subtype of BaseProvider<T>, but still can be totally unrelated to E which was chosen by the user.
An example:
class AnotherChildProvider : BaseProvider<ChildRealmModel>(context) {...}
val x = BaseProvider.create<ChildRealmModel, AnotherChildProvider>(context)
What is the type of x? According to the function signature, it must be AnotherChildProvider. But inside the function you return SomeChildProviderProvider, which CAN NOT be casted to AnotherChildProviderProvider.