Been trying to duplicate an iOS (Swift) solution in Android (Kotlin). It's fairly straight forward, but I'm not sure if I'm on the right track, or if I need to change up my Kotlin code entirely. (Note this solution is working in Swift)
Here is what's going on in Swift:
enum CarID: String, Codable {
case carHeader
case carView
case dataView
var viewControllerType: UIViewController.Type {
switch self {
case .carView: return CarViewController.self
case .carHeader: return CarHeaderViewController.self
case .dataView: return DataViewController.self
}
}
}
Now my attempt to match it in Kotlin:
enum class ModuleID(val type: String) {
CAR_HEADER("carHeader"),
CAR_VIEW("carView"),
DATA_VIEW("dataView");
val activityType: AppCompatActivity // Wrong return type?
get() = when(this) {
CAR_HEADER -> **CarHeader** // Error here
CAR_VIEW -> CarView
DATA_VIEW -> DataView
}
}
Note I am simply trying to use ActivityViewTypes, just as iOS is using the viewControllerType. The behavior should be near exact.
The error I am seeing reads: "Classifier 'CarHeader' does not have a companion object, and thus must be initialized here."
I attempted to create said companion object, but it just didn't seem necessary, so I backed off. Any suggestions are welcomed! I've also considered looking into a Sealed Class, but not sure if that is necessary here, either.
I have also not addressed the 'Codable - if someone wants to delve into that, that is fine as well.
Thanks!
What you want is a Type, and not an instance, so you'd probably want to return a class, so in Java terms:
UIViewController.Type ==> `Class<? extends Activity>`
With that in mind, following should work:
val activityType: Class<out Activity>
get() = when(this) {
CAR_HEADER -> CarHeader::class.java
CAR_VIEW -> CarView::class.java
DATA_VIEW -> DataView::class.java
}
}
Which you can even use like
startActivity(Intent(this, module.activityType))
Related
In the code below, i'd like to generalize it so I instead of viewBinding.editText.text and viewModel.property.price can use the same method for e.g viewBinding.secondEditText.text and viewModel.property.income.
I'm thinking exchanging viewBinding.editText.text for a variable defined in the primary constructor, but then I'd need the variable to contain a reference to viewBinding.editText.text/viewBinding.secondEditText.text etc. instead of containing a value.
Is this possible? I've looked at lengths for this but can't find anything useful.
fun updateProperty() {
//... other irrelevant code
if (viewBinding.editText.text.toString() != "") {
viewModel.property.price = viewBinding.editText.text.toString().toDouble()
}
//... other irrelevant code
}
You can pass parameters into a function, yeah!
This is the easy one:
fun updateProperty(editText: EditText) {
val contents = editText.text.toString()
}
simple enough, you just pass in whatever instance of an EditText and the function does something with it.
If you're just using objects with setters and getters, you can just define the type you're going to be using and pass them in. Depending on what viewmodel.property is, you might be able to pass that in as well, and access price and income on it. Maybe use an interface or a sealed class if there are other types you want to use - they need some commonality if you're going to be using a generalised function that works with them all.
Properties are a bit tricker - assuming viewmodel.property contains a var price: Double, and you didn't want to pass in property itself, just a Double that exists somewhere, you can do it like this:
import kotlin.reflect.KMutableProperty0
var wow: Double = 1.2
fun main() {
println(wow)
setVar(::wow, 6.9)
println(wow)
}
fun setVar(variable: KMutableProperty0<Double>, value: Double) {
variable.set(value)
}
>> 1.2
>> 6.9
(see Property references if you're not familiar with the :: syntax)
KMutableProperty0 represents a reference to a mutable property (a var) which doesn't have any receivers - just a basic var. And don't worry about the reflect import, this is basic reflection stuff like function references, it's part of the base Kotlin install
Yes, method parameters can also be references to classes or interfaces. And method parameters can also be references to other methods/functions/lambdas.
If you are dealing with cases that are hard to generalize, consider using some kind of inversion of control (function as parameter or lambda).
You add a lambda parameter to your updateProperty function
fun updateProperty(onUpdate: (viewBinding: YourViewBindingType, viewModel: YourViewModelType) -> Unit) {
//... other irrelevant code
// here you just call the lambda, with any parameters that might be useful 'on the other side'
onUpdate(viewBinding, viewModel)
//... other irrelevant code
}
Elsewhere in code - case 1:
updateProperty() { viewBinding, viewModel ->
if (viewBinding.editText.text.toString() != "") {
viewModel.property.price = viewBinding.editText.text.toString().toDouble()
}
}
Elsewhere in code - case 2:
updateProperty() { viewBinding, viewModel ->
if (viewBinding.secondEditText.text.toString() != "") {
viewModel.property.income = viewBinding.secondEditText.text.toString().toDouble()
}
}
Elsewhere in code - case 3:
updateProperty() { viewBinding, viewModel ->
// I am a totally different case, because I have to update two properties at once!
viewModel.property.somethingElse1 = viewBinding.thirdEditText.text.toString().toBoolean()
viewModel.property.somethingElse2 = viewBinding.fourthEditText.text
.toString().replaceAll("[- ]*", "").toInt()
}
You could then go even further and define a function for the first 2 cases, since those 2 can be generalized, and then call it inside the lambda (or even pass it as the lambda), which would save you some amount of code, if you call updateProperty() in many places in your code or simply define a simple function for each of them, and call that instead, like this
fun updatePrice() = updateProperty() { viewBinding, viewModel ->
if (viewBinding.editText.text.toString() != "") {
viewModel.property.price = viewBinding.editText.text.toString().toDouble()
}
}
fun updateIncome() = updateProperty() { viewBinding, viewModel ->
if (viewBinding.secondEditText.text.toString() != "") {
viewModel.property.income = viewBinding.secondEditText.text.toString().toDouble()
}
}
Then elsewhere in code you just call it in a really simple way
updatePrice()
updateIncome()
I'm new to coding in kotlin and want to implement an immutable class that represents a project with various fields inside.
The easiest way to do this is by using a data class and using the copy() method so that anytime one of the app user modifies a field it results in the backend in a call to the copy method with the modified field producing the new project.
My problem is that this way does not allow for prior checking of parameters (eg : limit string size of the owner, making sure the number of people added to the project is reasonable etc).
If this was java, I'd use a builder pattern but this seems to defeat the purpose of kotlin, and i've read articles that are positive to using builders in kotlin (https://www.baeldung.com/kotlin/builder-pattern)
and others that are completely against (https://code-held.com/2021/01/23/dont-use-builder-in-kotlin/).
I haven't found any way to "modify" the copy method and to add the parameter sanitization checks that are needed for each parameter. I would appreciate any "smooth" idea to implement this, if anybody has found it. The goal would also be to throw exeptions/sealed classes variables so that the app UI can tell the user what went wrong instead of a generic error message just mentioning that the project was not modified.
I agree with the second link. If you look at the comments on the Baeldung article, you'll see even they were convinced and pledged to revise the article.
You can throw exceptions in an init block but if these are exceptions that are not caused by programmer error, it would be more Kotlin-idiomatic to expose a single constructor-like function that returns a wrapper or just null for invalid input.
Examples:
data class Person(val name: String, val age: Int = 0) {
init {
if (age < 0) {
throw IllegalArgumentException("Age $age is less than 0.")
}
}
}
If you want to return a wrapper or nullable, a data class isn't suitable for preventing invalid input because the generated copy() function will always return a fully constructed object. Sadly, Kotlin does not support overriding the generated copy() function.
sealed class Result<T>
data class Success<T>(val value: T): Result<T>()
data class Failure<T>(val reason: String): Result<T>()
class Person private constructor(val name: String, val age: Int = 0) {
companion object {
fun build(name: String, age: Int = 0): Result<Person> {
return when {
age < 0 -> Failure("Age $age is less than 0.")
else -> Success(Person(name, age))
}
}
}
fun buildCopy(name: String = this.name, age: Int = this.age) = build(name, age)
}
I am new to mockk framework and trying to write a test case for below function
fun fromCityWeather(cityWeather: List<CityWeather>): HomeWeather {
return HomeWeather(
cityWeather.first().temperature,
cityWeather.first().description
)
}
CityWeather and HomeWeather are my 2 classes and both have temperature and weather as fields. This function is written inside companion object of another class.
Please help me to understand the logic to start with as I have tried multiple times writing the test case while referring the blogs on internet and none worked.
You don't need mockk:
#Test
fun testFromCityWeather() {
val weatherList = listOf(CityWeather("30", "degrees"), CityWeather("12", "degrees"))
val expected = HomeWeather("30", "degrees")
assertEquals(expected, fromCityWeather(weatherList))
}
(Assuming temperature and description are strings)
Android studio is giving me a squiggly saying that I should/could turn this into a lambda. I'm just getting back into my android.
popup.setOnMenuItemClickListener(object : PopupMenu.OnMenuItemClickListener {
override fun onMenuItemClick(item: MenuItem): Boolean {
if (item.itemId === R.id.action_vitals) {
val vitalsIntent = Intent(this#DashboardActivity, VitalsActivity::class.java)
vitalsIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
startActivity(vitalsIntent)
}
if (item.itemId === R.id.action_devices) {
val devicesIntent = Intent(this#DashboardActivity, DevicesActivity::class.java)
devicesIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
startActivity(devicesIntent)
}
return false
}
})
This is the squiggly "object : PopupMenu.OnMenuItemClickListener"
There are a couple of comments and answers suggesting to use alt + enter in IntelliJ/Android Studio and that will handle converting your code into a Lambda for you. However, it might be worth covering why it is suggesting that to you.
A lambda is a function that isn't declared e.g. fun someFunction() but instead is immediately passed as a parameter to another function. This lambda will then be executed by some other code elsewhere in the app. Essentially a Lambda is a shorthand function e.g:
val lambda: () -> Unit = {
// Some code could go inside this Lambda here
}
An important concept for you here is a SAM (Single Abstract Method) type. This simply refers to an interface that defines a single abstract function that needs to be implemented. In your example PopupMenu.OnMenuItemClickListener is a Java interface that has a single abstract method void onMenuItemClick(MenuItem item). SAM types can be written in shorthand with the body of the lambda becoming the body of abstract function.
You've correctly written this as an anonymous object which is fine; but it could be written more concisely with a lambda which Android studio is suggesting. Another helpful tidbit, in Kotlin if a function or lambda is the sole or final parameter in a list of parameters it can be moved outside of the braces of the function or they can be removed entirely.
So your code will have been converted to something like this:
enterpopup.setOnMenuItemClickListener {
if (item.itemId === R.id.action_vitals) {
val vitalsIntent = Intent(this#DashboardActivity, VitalsActivity::class.java)
vitalsIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
startActivity(vitalsIntent)
}
if (item.itemId === R.id.action_devices) {
val devicesIntent = Intent(this#DashboardActivity, DevicesActivity::class.java)
devicesIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
startActivity(devicesIntent)
}
return false
}
You can use Alt+Enter to show a list of "suggestions" (called inspections in JetBrains language). Selecting the suggestion will perform the conversion automatically for you.
IntelliJ 2019.2 brings an improvement to this feature (which should come to Android​ Studio soon), showing more info about the top suggestion and adding Alt+Shift+Enter as a shortcut to directly apply it (bypassing the list)
https://www.jetbrains.com/idea/whatsnew/#v2019-2
I'm trying to figure out if something is possible. Generally, what I'm trying to do is get a class type of a subclass from within the companion object of the superclass ... In the snipped below, treat the __ as what I need
companion object
{
fun fromSnapshot(snapshot: DataSnapshot): __
{
val model = snapshot.getValue(__)
model.key = snapshot.key
// ...
return model
}
}
Some background ... DataSnapshot is from Firebase, and snapshot.getValue() takes a Class<T>. If I were trying to create an instance of, say, a TestModel, code would be as follows
companion object
{
fun fromSnapshot(snapshot: DataSnapshot): TestModel
{
val model = snapshot.getValue(TestModel::java.class)
model.key = snapshot.key
// ...
return model
}
}
I'm not really sure if what I'm asking is possible in Kotlin. I'm pretty sure it isn't in Java. I hate to mention it, but in Swift, this would be accomplished with what I call "big-S self," or Self, which is the class type of instance self. If you don't know Swift, self is equivalent to Java and Kotlin's this.
Any help would be hugely appreciated!
From your code, it seems to be a very generic function. It doesn't matter what T is and in which companion object this function lives, so I have another version:
inline fun <reified T : FirebaseModel> DataSnapshot.toModelOfType() =
getValue(T::class.java).also { it.key = this.key}
It can be used like this:
someSnapshot.toModelOfType<SomeFirebaseModel>()
instead of your
FirebaseModel.fromSnapshot<SomeFirebaseModel>(someSnapshot)
or with imports
fromSnapshot<SomeFirebaseModel>(someSnapshot)
I prefer mine because it's shorter than your version without imports and more fluent than your version with imports.
I personally suggest Prefer extension functions over Java style utility functions.
Even though I sat on this for days without posting a question, I figured it out less than an hour after posting this question. This can be accomplished with a reified generic type, which allows for usage of the generic type from within a function, however these can only be used as inline functions. Here's my solution
companion object
{
inline fun <reified T : FirebaseModel> fromSnapshot(snapshot: DataSnapshot): T
{
val model = snapshot.getValue(T::class.java)
model.key = snapshot.key
return model
}
}