Traditionally, in Groovy, it was possible to define flavor-specific variables in the ext
{} block, but switching to Kotlin DSL it seems that the extra map has the project scope.
It looks like it is possible to force a flavor scope for extras by using:
productFlavors {
register("flavor1") {
require(this is ExtensionAware)
...
}
register("flavor2") {
require(this is ExtensionAware)
...
}
}
(source: https://github.com/gradle/kotlin-dsl-samples/issues/1254)
But if it needs to be used later, for example to tweak the variables based on buildType like this:
variants.forEach { variant ->
val flavor = variant.productFlavors[0]
val size = ?? // extra??
variant.buildConfigField("String", "SIZE", size)
}
how would these flavor-scoped references to extra be used?
Currently, only one working method to access ext properties on ProductFlavor is this way
variant.productFlavors.forEach { flavor ->
require(flavor is ReadOnlyProductFlavor)
val properties = (flavor.getProperty("ext") as DefaultExtraPropertiesExtension).properties
}
Or maybe more clear
val flavor = variant.productFlavors[0] as ReadOnlyProductFlavor
val extra = flavor.getProperty("ext") as DefaultExtraPropertiesExtension
extra.get("myPropertyName")
It just does the same thing what Groovy does when you call the non-existing property ext
I created a feature request for making it easier
https://issuetracker.google.com/issues/161115878
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 was used to override XML resources in values within specific app variants but i cannot do that anymore in jetpack compose so i was wondering which is a good approach with the new library without using any XML at all.
My theming is in a module and that module doesn't know anything about the app variants and i would like it to stay so. Which means that i cannot create a AppVariantColors class for each variant since i would not be able to reference it.
Should i pass the palette to the theme composable?
EDIT: what i am doing now is the following:
First i define a palette class with a set of color variants i will then use in the real variants:
enum class AppColorPalette {
DARK_BLUE,
DARK_RED,
LIGHT_BLUE,
LIGHT_RED
}
Then i create an app theme passing the palette
#Composable
fun AppTheme(
dims: AppDims = appDims(),
palette: AppColorPalette = AppColorPalette.DARK_BLUE,
colors: AppColors = appColors(colorPalette = palette),
typography: AppTypography = appTypography(colors, dims),
shapes: AppShapes = appShapes(),
content: #Composable () -> Unit,
) {
CompositionLocalProvider(
LocalAppColors provides colors,
LocalAppDims provides dims,
LocalAppShapes provides shapes,
LocalAppTypography provides typography,
) {
MaterialTheme(
colors = colors.materialColors,
typography = typography.materialTypography,
shapes = shapes.materialShapes,
content = content,
)
}
}
Finally i create app Colors composable based on the palette
#Composable
fun appColors(colorPalette: AppColorPalette): AppColors = when (colorPalette) {
AppColorPalette.DARK_BLUE -> darkAppColorsBlueVariant
...
}
Where colors are defined something like this:
private val darkAppColorsBlueVariant = darkAppColors.copy(
highEmphasis = AppColorsPalette.WhiteA100,
midEmphasis = AppColorsPalette.WhiteA50,
materialColors = darkColors(
primary = AppColorsPalette.DarkGrey900,
)
)
Basically doing so i can use the BuildConfig approach having to just select the color variant based on the theme
You can use the same approach.
Just config your build variant or product flavor in the build.gradle file then in your module define the MaterialTheme object (using for example the Themes.kt file in the different modules):
MaterialTheme(
colors = …,
typography = …,
shapes = …
) {
// app content
}
Or use your approach described in the updated question, using a single MaterialTheme object with the different colors defined in the single variants.
One way could be through BuildConfig fields on your build.gradle (app) file.
First define your flavors:
android {
// other stuff
flavorDimensions "base"
productFlavors {
cocacola {
dimension "base"
// cocacola is red :)
buildConfigField "int", "accentColor", "0xFF0000"
}
pepsi {
dimension "base"
// pepsi is blue
buildConfigField "int", "accentColor", "0x00FF00"
}
}
}
This will autogenerate a BuildConfig.java that you can access through Kotlin:
public final class BulildConfig {
public static final int accentColor = 0xFF000; // depending on the flavor built
}
Then you can reference this static values from the place you define the material theme similar to how #Philip Dukov suggested
private val AppPalette = lightColors(
accent = Color(BuildConfig.accentColor)
)
MaterialTheme(
colors = AppPalette,
typography = …,
shapes = …,
content = …
)
I'd like to know how I can change variables INSIDE a Composable via a method.
Of course i can do something like:
var test: String by remember { mutableStateOf("hello") }
and can change it like I want to, like (although it is a stupid example)
[stuff...].pointerInput(Unit) {
detectDragGestures(...)
{ change, dragAmount ->
test=dragamount.toString()
}
}
but how would i change the variable with some complicated method e.g.
[stuff...].pointerInput(Unit) {
detectDragGestures(...)
{ change, dragAmount ->
changeText(dragAmount)
}
}
I can only use methods outside of the composable to assign it to the value, aka
test=getMyNewTest(dragAmount)
But how can i change my 'fields' inside a composable, so that i can modify 'test' directly in my method?
If the method can see the a mutable variable (i.e. share a lexical scope) it can change it. For changeText to be able to change test, it must either have test in scope or receive a mutable reference to test as a parameter. Compose doesn't change this.
Any answer to this question outside of compose (e.g. class scopes, modules scopes, global scopes, closure capture, reference passing, etc.) works in compose as well.
For example, you could define the function as local to the composable function as,
#Composable
fun Example() {
var test by remember { mutableStateOf("hello") }
fun changeText(amount: Float) {
test = amount.toString()
}
...
[stuff...].pointerInput(Unit) {
detectDragGestures(...)
{ change, dragAmount ->
changeText(dragAmount)
}
}
...
}
I've been writing in Kotlin for a while, and I get used to use next pattern:
variable.addSomething(object: TargetType() { ...code... })
or
var variable = object: TargetType() { ...code... }
(if i'm not missing something)
Is it possible to somehow use this pattern in Swift ? And how is it called ? :)
Edit:
What I actually want to do - to store preconfigured RxSwift.SingleEvent in a let / var inside object and reuse it later multiple times.
In code, as I imagine, it should look like that:
private var observer = SingleEvent<Response>(ok_callback, error_callback) {
override success(el: Element) {
ok_callback(el)
super.success(el)
}
override error(er: Error) {
self.onErrorRetry(er, callback)
}
}
And if retry after some magic works - simply call my callback and return back :)
Its seems to be trailing closure. Adapted from Swift programming language - Closures:
If you need to pass a closure expression to a function as the
function’s final argument and the closure expression is long, it can
be useful to write it as a trailing closure instead. A trailing
closure is written after the function call’s parentheses, even though
it is still an argument to the function. When you use the trailing
closure syntax, you don’t write the argument label for the closure as
part of the function call.
Let's code it:
Simply, all you have to do is just to create a function which its last argument is a closure:
func doSomething(firstParameter: Any, closure: () -> Void) { }
thus you could call it as:
doSomething(firstParameter: "whatever") {
// ...
}
Nothing special goes here, it is a cool feature from the Swift language to "trail" closure parameter if its the last one in the function signature.
In case of initialization, it is almost the same:
struct MyObject {
init(firstParameter: Any, closure: () -> Void) { }
}
let variable = MyObject(firstParameter: "whatever") { }
Certainly, this pattern is followed by many other functions in the language, but here is an example of the merge method for the Dictionary, you could recognize how you could type it in more than one way as mentioned in the answers of: Map Dictionary Keys to add values - Swift.
Update:
If you are aiming to use it as constant/variable -to be passed for a function for example-, you could do it like this:
let variable: (String) -> Void = { name in
print("The name is: \(name)!")
}
At this point, variable type is (String) -> Void which means that its a constant that could be passed somewhere else; Consider the following method:
func doSomething(closure: (String) -> Void) {
closure("Nikita")
}
Because doSomething takes a parameter of type (String) -> Void, you could do:
doSomething(closure: variable) // The name is: Nikita!
instead of calling it as:
doSomething { name in
print("The name is: \(name)!")
}
which prevents the boilerplate code.
I'm writing a simple import application and need to read a CSV file, show result in a grid and show corrupted lines of the CSV file in another grid.
Is there any built-in lib for it or any easy pythonic-like way?
I'm doing it on android.
[Edit October 2019] A couple of months after I wrote this answer, Koyama Kenta wrote a Kotlin targeted library which can be found at https://github.com/doyaaaaaken/kotlin-csv and which looks much better to me than opencsv.
Example usage: (for more info see the github page mentioned)
import com.github.doyaaaaaken.kotlincsv.dsl.csvReader
fun main() {
csvReader().open("src/main/resources/test.csv") {
readAllAsSequence().forEach { row ->
//Do something
println(row) //[a, b, c]
}
}
}
For a complete minimal project with this example, see https://github.com/PHPirates/kotlin-csv-reader-example
Old answer using opencsv:
As suggested, it is convenient to use opencsv. Here is a somewhat minimal example:
// You can of course remove the .withCSVParser part if you use the default separator instead of ;
val csvReader = CSVReaderBuilder(FileReader("filename.csv"))
.withCSVParser(CSVParserBuilder().withSeparator(';').build())
.build()
// Maybe do something with the header if there is one
val header = csvReader.readNext()
// Read the rest
var line: Array<String>? = csvReader.readNext()
while (line != null) {
// Do something with the data
println(line[0])
line = csvReader.readNext()
}
As seen in the docs when you do not need to process every line separately you can get the result in the form of a Map:
import com.opencsv.CSVReaderHeaderAware
import java.io.FileReader
fun main() {
val reader = CSVReaderHeaderAware(FileReader("test.csv"))
val resultList = mutableListOf<Map<String, String>>()
var line = reader.readMap()
while (line != null) {
resultList.add(line)
line = reader.readMap()
}
println(resultList)
// Line 2, by column name
println(resultList[1]["my column name"])
}
Dependency for Gradle: compile 'com.opencsv:opencsv:4.6' or for Gradle Kotlin DSL: compile("com.opencsv:opencsv:4.6") (as always, check for latest version in docs).
In terms of easiness, kotlin written csv library is better.
For example, you can write code in DSL like way with below library that I created:
https://github.com/doyaaaaaken/kotlin-csv
csvReader().open("test.csv") {
readAllAsSequence().forEach { row ->
//Do something with the data
println(row)
}
}
Use opencsv.
This is gonna work like a charm for reading a CSV file.
As far as logging the corrupted lines is concerned you can do it using this logic.
while(input.hasNextLine())
{
try
{
//execute commands by reading them using input.nextLine()
}
catch (ex: UserDefinedException)
{
//catch/log the exceptions you're throwing
// log the corrupted line the continue to next iteration
}
}
Hope this helps.
I used net.sourceforge.javacsv with my Kotlin code for parsing CSV files. It is a "java" library but within kotlin it is fairly straightforward to work with it like
val reader = CsvReader("/path/to/file.csv").apply {
trimWhitespace = true
skipEmptyRecords = true
readHeaders()
}
while (reader.readRecord()) {
// do whatever
}
Frankly speaking, it is quite easy to make a simple reader in Kotlin using modern Java features, check this (REMEMBER to handle BOM :-)):
fun processLineByLine(csv: File, processor: (Map<String, String>) -> Unit) {
val BOM = "\uFEFF"
val header = csv.useLines { it.firstOrNull()?.replace(BOM, "")?.split(",") }
?: throw Exception("This file does not contain a valid header")
csv.useLines { linesSequence ->
linesSequence
.drop(1)
.map { it.split(",") }
.map { header.zip(it).toMap() }
.forEach(processor)
}
}
Than you can use it as follows (depends on your file structure):
processLineByLine(File("./my-file.csv")) { row ->
println("UserId: ${row["userId"]}")
println("Email: ${row["email"]}")
}
If you prefer to use your own data class for each row you should have a look at my solution https://github.com/gmuth/ipp-client-kotlin/blob/master/src/main/kotlin/de/gmuth/csv/CSVTable.kt
data class User(
val name: String,
val phone: String,
val email: String
) {
constructor(columns: List<String>) : this(
name = columns[0],
phone = columns[1],
email = columns[2]
)
}
CSVTable.print(FileInputStream("users.csv"))
val userList = CSVTable(FileInputStream("users.csv"), ::User).rows
I know i'm a bit late, but I recently had problems with parsing CSV and there seemed to be no library good enough for what I was looking for, so I created my own called Kotlin CSV stream.
This library is special because it doesn't throw exceptions on an invalid input, but returns in the result instead, which might be useful in some cases.
Here is an example of how easy it is to use
val reader = CsvReader()
.readerForType<CsvPerson>()
val people = reader.read(csv).map { it.getResultOrThrow() }.toList()
For version of commons-csv version 1.9.0, have implemented below code to get results.
It uses CSVBuilder and CSVFormat to get records with skip headers and auto-identify headers on basis of first row.
fun csvReader(file: MultipartFile): ResultListObject? {
var result = ResultListObject()
var csvFormat=CSVFormat.Builder.create().setHeader().setSkipHeaderRecord(true).build()
var csvRecords = CSVParser(file.inputStream.bufferedReader(), csvFormat)
csvRecords.forEach{csvRecords->
rowRecord.field1=records.get("field1")
rowRecord.field2=records.get("field2")
...
...
result.add(rowRecord)
}
return result
}