I am developing Android project in Kotlin. I want to create a model class that implements Parcelable interface. This is what I tried:
#Parcelize
data class School(
#Expose val name: String?,
#Expose val address: String?,
): Parcelable
But I get compiler error saying that "Class School is not abstract and does not implement abstract memeber public abstract fun writeToParcel(p0: Parcel!, p1: Int):Unit defined in android.os.Parcelable".
I understand what the error is saying. But how to get rid of this error? My Kotlin version is 1.3.50
Add
androidExtensions {
experimental = true
}
to your Android block within your app build.gradle.
check you have Added this
androidExtensions {
experimental = true
}
and try to implement Parcelable like this
class School(var name:String, var address:String):Parcelable
pass data like this
val school = School("demo","demo")
val intent = Intent(this, activityB::class.java)
intent.putExtra("schooldata",school)
startActivity(intent)
get like this
var school = intent.extras.getParcelable<School>("schooldata")
schooldata.setText("${school.name}"+"\n"+"${school.address}")
Related
I'm trying to use Ktor in my project. I made everything as it's written in documentation, but it doesn't work
Dependencies:
implementation "io.ktor:ktor-client-core:1.5.4"
implementation "io.ktor:ktor-client-android:1.5.4"
implementation "io.ktor:ktor-client-serialization-jvm:1.5.4"
HTTPClient and request:
val client = HttpClient {
install(JsonFeature){
serializer = KotlinxSerializer()
}
defaultRequest {
url {
protocol = URLProtocol.HTTPS
host = "testtesttest.zapto.org"
}
}
}
suspend fun getUsersList() = client.get<User>{
url("/users/list")
}
User class:
import com.kepler88d.icthack2app.model.enumerations.UserSpecialization
import kotlinx.serialization.Serializable
#Serializable
data class User(
val id: Int,
val password: String,
val rating: Float,
val specialization: UserSpecialization,
val firstName: String,
val lastName: String,
val profileDescription: String,
val githubProfileLink: String,
val projectIdList: MutableList<Int>,
val replyIdList: MutableList<Int>,
val tgId: String,
)
UserSpecialization class:
import kotlinx.serialization.Serializable
#Serializable
enum class UserSpecialization {
IOS_DEVELOPER, ANDROID_DEVELOPER, WEB_BACKEND_DEVELOPER, WEB_FRONTEND_DEVELOPER
}
When i'm trying to deserialize JSON into object it crashes with this error:
kotlinx.serialization.SerializationException: Serializer for class 'User' is not found.
Mark the class as #Serializable or provide the serializer explicitly.
The strange thing is that the enum class Android studio see as serializable, but data class doesn't
enum class
data class
The solution was to add plugin
id 'kotlinx-serialization'
I did this for Android but it may also work for ktor. At first, add this line to dependencies in the build project gradle:
classpath "org.jetbrains.kotlin:kotlin-serialization:1.5.21"
Add also these to the build app gradle:
plugins {
...
id 'kotlinx-serialization'
}
dependencies {
...
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.2'
}
Please note version numbers might be different.
I have a parcelable team class
#Parcelize
class Team(var name: String, var teamMembers: List<String>, var id: UUID): Parcelable
I have a service that returns a list of (currently hardcoded) Teams:
#Module
class TeamInfoModule #Inject constructor(): ITeamInfoModule {
#Provides
override fun getAllTeamData(): List<Team> { ... }
}
I want to be able to pass this list of teams into a Fragment from an activity like so:
class MainActivity: AppCompatActivity() {
#Inject
lateinit var teamInfoModule: TeamInfoModule;
lateinit var team: Team;
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DaggerServiceModuleComponent.create().inject(this)
val bundle = Bundle()
val teamArrayList: List<Team> = this.teamInfoModule.getAllTeamData()
val homeFragment = HomeFragment()
bundle.putParcelable("teamData", teamArrayList)
homeFragment.arguments = bundle
}
}
This throws an error of: Type Mismatch. Required: Parcelable? Found: List<Team>.
I know that that a single team can be passed to my Fragment as it doesn't throw an error.
My question is, is there a utility that I haven't found that can somehow serialize a List to a Parcelable? I had the idea of creating a custom TeamListClass that also implements #Parcelize but I wanted to ask here before I went off and wrote code that I didn't need. Maybe something similar to a JS' Array.map() that will pass each Parcelable into the bundle?
You should use:
bundle.putParcelableArrayList("teamData", ArrayList(teamArrayList))
Convert the list to arrayList using ArrayList(teamArrayList)
bundle.putParcelableArrayList("teamData", ArrayList(teamArrayList))
The class MySettings include two embed class in Code A, how can I create a oject of MySettings ?
It seems that fun initA() can't instance of two embed class.
I realize that the construct of Code A is wrong, so I write Code B, I don't know whether there is a good way!
Code A
class MySettings(val _id: Long) {
data class MyBluetooth(
val status: Boolean = false
)
data class WiFiDef(
val name: String,
val status: Boolean = false
)
}
fun initA(){
var myObject =MySettings(10L)
}
Code B
class MySettings(val _id: Long) {
var aMyBluetooth: MyBluetooth? =null
var aWiFiDef: WiFiDef?=null
}
data class MyBluetooth(
val status: Boolean = false
)
data class WiFiDef(
val name: String,
val status: Boolean = false
)
fun initB(){
var myObject =MySettings(10L)
myObject.aMyBluetooth=MyBluetooth(false)
myObject.aWiFiDef=WiFiDef("name",true)
}
As far as your question is concerned Code A is absolutely right.
But if we draw a comparison between your Code A and Code B, there are different scenarios in which they are best suited.
Scenario 1: Good Programming Practice
If you want to follow good programming practice in Kotlin, then follow Code B as it is a good practice to write code that is reusable. And one more thing, you can instantiate your object in code B like this.
fun initB(){
var myObject =MySettings(10L, aMyBluetooth=MyBluetooth(false),
aWiFiDef=WiFiDef("name",true))
}
Scenario 2: Inner Class
Your Code A is useful in a situation where you want to implement inner class in kotlin. Inner class is able to access members of outer class. But in this scenario, data class cannot be inner.
class MySettings(val _id: Long) {
inner class MyBluetooth(
val status: Boolean = false
)
inner class WiFiDef(
val name: String,
val status: Boolean = false
)
}
fun initA(){
var myObject =MySettings(10L)
}
I hope this clears your query.
You can keep your data classes inside MySettings if you combine somehow Code A and Code B:
class MySettings(val _id: Long) {
val blueTooth = MyBluetooth(true)
val wiFiDef = WiFiDef("wifi", true)
data class MyBluetooth(
val status: Boolean = false
)
data class WiFiDef(
val name: String,
val status: Boolean = false
)
}
fun initA(){
val myObject = MySettings(10L)
val myBlueTooth = myObject.blueTooth
val myWiFiDef = myObject.wiFiDef
}
After that it's up to you to decide by changing all or any of val to var inside MySetings how to use the instances of the data classes. But this is a way to expose them.
I'd go with approach like this:
Make use of Kotlin constructors, which can take class-scope variables. Just mark those nullable (via ?) inside constructor and let the user decide wether he/she wants to initialise those variables or not
Make all settings (bluetooth and wifi) extend from sealed class, especially since you have common variables between them.
To sum up, I would write this code like this:
class MySettings(val id: Long, val bluetooth: Setting.MyBluetooth? = null, val wifi: Setting.WifiDef? = null) {
sealed class Setting(val status: Boolean = false) : Serializable {
class WifiDef(
status: Boolean,
val name: String
): Setting(status)
class MyBluetooth(
status: Boolean
): Setting(status)
}
}
fun init() {
val mySettings = MySettings(10L, bluetooth = MySettings.Setting.MyBluetooth(true))
val bluetooth = mySettings.bluetooth
val wifi = mySettings.wifi
}
Talking about other ways to do this, I would highly recommend not to hard code any values inside your class, especially not when you have some instances of classes inside. This will be hideous to refactor later on, if something changes
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
fun launchNextScreen(context: Context, people: People): Intent {
val intent = Intent(context, NextScreenActivity::class.java)
intent.putExtra(EXTRA_PEOPLE, (Parcelable) people)
//intent.putExtra(EXTRA_PEOPLE, people as Parcelable)
//intent.putExtra(EXTRA_PEOPLE, people)
// tried above all three ways
return intent
}
I tried the above code to pass an instance of the People class via intent using kotlin, but I am getting an error.
What am I doing wrong?
First, make sure the People class implements the Serializable interface:
class People : Serializable {
// your stuff
}
Inner fields of People class must also implement the Serializable interface, otherwise you'll get runtime error.
Then it should work:
fun launchNextScreen(context: Context, people: People): Intent {
val intent = Intent(context, NextScreenActivity::class.java)
intent.putExtra(EXTRA_PEOPLE, people)
return intent
}
To receive people back from Intent you'll need to call:
val people = intent.getSerializableExtra(EXTRA_PEOPLE) as? People
Implement Serializable in the object:
data class Object (
var param1: Int? = null,
var param2: String? = null
) : Serializable
or
class Object : Serializable {
var param1: Int? = null,
var param2: String? = null
}
Then, you can pass the object using Intent:
val object = Object()
...
val intent = Intent(this, Activity2::class.java)
intent.putExtra("extra_object", object as Serializable)
startActivity(intent)
Finally, in Activity2 you get the object with:
val object = intent.extras.get("extra_object") as Object
Found a better way of doing this:
In your gradle:
apply plugin: 'org.jetbrains.kotlin.android.extensions'
android {
androidExtensions {
experimental = true
}
}
In your data class:
#Parcelize
data class Student(val id: String, val name: String, val grade: String) : Parcelable
In source activity:
val intent = Intent(context, Destination::class.java)
intent.putExtra("student_id", student)
context.startActivity(intent)
In destination activity:
student = intent.getParcelableExtra("student_id")
Parcelize is no longer experimental, as of Kotlin 1.3.60+!
Define a data class, adding #Parcelize annotation and extending Parcelable:
#Parcelize
data class People(val id: String, val name: String) : Parcelable
To add to intent:
val intent = Intent(context, MyTargetActivity::class.java)
intent.putExtra("people_data", student)
context.startActivity(intent)
In MyTargetActivity:
val people: People = intent.getParcelableExtra("people_data")
If you haven't yet, enable extensions in your app's build.gradle. This also enables data binding and other great advanced features
apply plugin: 'kotlin-android-extensions'
I found a better way to pass an object from one activity to another using Parcelable, it is faster than Serialization
Android: Difference between Parcelable and Serializable?
first, add these lines of code inside build.gradle(app)
apply plugin: 'kotlin-android-extensions' in app.grable
#Parcelize
class Model(val title: String, val amount: Int) : Parcelable
passing with Intent--->
val intent = Intent(this, DetailActivity::class.java)
intent.putExtra(DetailActivity.EXTRA, model)
startActivity(intent)
Getting from intent --->
val model: Model = intent.getParcelableExtra(EXTRA)
This post might be useful for what you intend to do:
Is there a convenient way to create Parcelable data classes in Android with Kotlin?
Basically, kotlin provides some little extras:
#Parcelize
class User(val firstName: String, val lastName: String) : Parcelable
See the post for more info.
in kotlin to start activity and pass some data you could try something like this:
startActivity(intentFor<NameOfActivity>(STRING to data))
Have Fun
you have to add the type inside <>...its working for me
val people = intent.getParcelableExtra<People>(EXTRA_PEOPLE) as People
In your PeopleDetailActivity activity, initialize viewModel in onCreate
viewModel = this#PeopleDetailActivity.viewModel.apply { postPeopleId(getPeopleFromIntent().id) }
And also initialize this method :
private fun getPeopleFromIntent() = intent.getParcelableExtra<People>(peopleId) as People
Write below code for passing the intent extras :
companion object {
private const val objectId = "people"
fun startActivityModel(context: Context?, people: People) {
if (context != null) {
val intent = Intent(context, PeopleDetailActivity::class.java).apply {
putExtra(
objectId,
people
)
}
context.startActivity(intent)
}
}
}
Call the above method in your PeopleListViewHolder onClick
override fun onClick(v: View?) = PeopleDetailActivity.startActivityModel(context(), people)
Your People class needs to implement Parcelable like this:
class People(): Parcelable{
// stuff
}
android studio will show you an error. Simple move your cursor to "People" and hit alt+enter. It should now show the option to generate a Parcelable implementation.
This generated code will contain something like
parcel.writeString(member1)
parcel.writeInt(member2)
and somewhere else
member1=parcel.readString()
member2=parcel.readInt()
Make sure that all members are contained in these methods and when modifying it, make sure that read and write is happening in the exact same order.