The main question is: is lparams simply gone from Anko, or am I doing something terribly wrong? The following snippet fails to compile:
verticalLayout {
}.lparams(width = matchParent, height = matchParent) {
topMargin = dip(10)
}
While this works without any problems:
verticalLayout {
layoutParams = LinearLayout.LayoutParams(matchParent, matchParent).apply {
topMargin = dip(10)
}
}
I wouldn't mind the second option too much, but you have to specify the layout type when generating the params, which can get a bit tiresome (and is also more brittle than the original solution).
I haven't found anything on the Anko GitHub page, the changelog, or by skimming recent commits. Here's the full UI class for reference:
class ReviewsFragmentUi(ctx: AnkoContext<ReviewsFragment>) : AnkoComponent<ReviewsFragment> {
override fun createView(ui: AnkoContext<ReviewsFragment>) = ui.apply {
verticalLayout {
layoutParams = LinearLayout.LayoutParams(matchParent, matchParent).apply {
topMargin = dip(10)
}
}
}.view
}
Relevant Gradle entries (I'm using Kotlin 1.0.0-beta-3595):
ext.versions = [
anko : '0.8.1',
]
compile "org.jetbrains.anko:anko-common:$versions.anko",
compile "org.jetbrains.anko:anko-sdk21:$versions.anko",
compile "org.jetbrains.anko:anko-support-v4:$versions.anko",
compile "org.jetbrains.anko:anko-design:$versions.anko",
compile "org.jetbrains.anko:anko-appcompat-v7:$versions.anko",
compile "org.jetbrains.anko:anko-cardview-v7:$versions.anko",
compile "org.jetbrains.anko:anko-recyclerview-v7:$versions.anko",
compile "org.jetbrains.anko:anko-gridlayout-v7:$versions.anko",
As a follow-up question: if lparams is indeed gone, then is there a more elegant replacement than what I'm already doing?
Apparently lparams is still there, but cannot be used as an extension function for the outermost layout:
So the following code fails:
override fun createView(ui: AnkoContext<ReviewsFragment>) = ui.apply {
verticalLayout {
// Layout elements here
}.lparams {
// Layout params here
}
}.view
But this compiles fine:
override fun createView(ui: AnkoContext<ReviewsFragment>) = ui.apply {
verticalLayout {
lparams {
// Layout params here
}
// Layout elements here
verticalLayout { }.lparams {
// lparams works fine if there is a parent layout
}
}
}.view
It's worth noting that using the non-tailing version of lparams is discouraged for inner layouts: it will create the wrong sublass of LayoutParams when the nested layouts are of different types. For a complete discussion, see this GitHub Issue.
Why don't you use the most recent way to write createView() method?
I think the following solves your problem:
override fun createView(ui: AnkoContext<ReviewsFragment>) : View = with(ui) {
return verticalLayout {
// Layout elements here
}.lparams {
// Layout params here
}
}
Related
I set up navigation, pagination and use flow to connect ui with model. If simplify, my screen code looks like this:
#Composable
MainScreen() {
val listState = rememberLazyListState()
val lazyItems = Pager(PagingConfig(...)) { ... }
.flow
.cachedIn(viewModelScope)
.collectAsLazyPagingItems()
LazyColumn(state = listState) {
items(lazyItems, key = { it.id }) { ... }
}
}
And here is my NavHost code:
NavHost(navController, startDestination = "mainScreen") {
composable("mainScreen") {
MainScreen()
}
}
But when i navigate back to MainScreen from another screen or just opening the drawer, data is loaded from DataSource again and i see noticeable blink of LazyColumn.
How to avoid reloading data?
Your code gives me the following error for cachedIn:
Flow operator functions should not be invoked within composition
You shouldn't ignore such warnings.
During transition Compose Navigation recomposes both disappearing and appearing views many times. This is the expected behavior.
And your code creates a new Pager with a new flow on each recomposition, which is causing the problem.
The easiest way to solve it is using remember: it'll cache the pager flow between recompositions:
val lazyItems = remember {
Pager(PagingConfig(/* ... */)) { /* ... */ }
.flow
.cachedIn(viewModelScope)
.collectAsLazyPagingItems()
}
But it'll still be reset during configuration change, e.g. device rotation. The best way to prevent this is moving this logic into a view model:
class MainScreenViewModel : ViewModel() {
val pagingFlow = Pager(PagingConfig(/* ... */)) { /* ... */ }
.flow
.cachedIn(viewModelScope)
}
#Composable
fun MainScreen(
viewModel = viewModel<MainScreenViewModel>()
) {
val lazyItems = viewModel.pagingFlow.collectAsLazyPagingItems()
}
I want to tinker with Kotlin Compose for Web a bit.
In some of my past web projects, I made use of some web components of the Clarity Design System (CDS).
In a JavaScript or TypeScript project,
you first need to install both npm packages#cds/core and #cds/city.
Secondly, you have to include some global stylesheets, e.g. via HTML or sass-import.
For each component you want to use, you need to import the corresponding register.js.
Lastly, you can include the component in your HTML like any other tag:
<cds-button>Click me!</cds-button>
I tried to replicate the steps with Kotlin Compose for Web, but wasn't able to get it to work.
Any help appreciated!
Okay, I've got it to work now, which included several steps.
Install npm dependencies
kotlin {
...
sourceSets {
val jsMain by getting {
dependencies {
// dependencies for Compose for Web
implementation(compose.web.core)
implementation(compose.runtime)
// dependencies for Clarity Design System
implementation(npm("#cds/core", "5.6.0"))
implementation(npm("#cds/city", "1.1.0"))
// dependency for webpack - see step 3
implementation(npm("file-loader", "6.2.0"))
}
}
...
}
}
Enable css support
This seems to be required, in order to include the global stylesheets.
kotlin {
js(IR) {
browser {
...
commonWebpackConfig {
cssSupport.enabled = true
}
}
...
}
...
}
Add support for .woff2 files included in stylesheet of Clarity
The stylesheet of CDS include font files of type .woff2, whose support in webpack must be configured.
This can be achieved by creating the file webpack.config.d/support-fonts.js
at the project root with the following content:
config.module.rules.push({
test: /\.(woff(2)?|ttf|eot|svg|gif|png|jpe?g)(\?v=\d+\.\d+\.\d+)?$/,
use: [{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'fonts/'
}
}]
});
Include global stylesheets
external fun require(module: String): dynamic
fun main() {
require("modern-normalize/modern-normalize.css")
require("#cds/core/global.min.css")
require("#cds/core/styles/module.shims.min.css")
require("#cds/city/css/bundles/default.min.css")
...
}
Import register.js for desired web component
external fun require(module: String): dynamic
fun main() {
...
require("#cds/core/button/register.js")
...
}
Create #Composable for the web component
Sadly this solution makes use of APIs marked as #OptIn(ComposeWebInternalApi::class),
which stands for "This API is internal and is likely to change in the future".
Any hints on how this may be implemented without relying on internal APIs are appreciated.
#Composable
fun CdsButton(
status: CdsButtonStatus = CdsButtonStatus.Primary,
attrs: AttrBuilderContext<HTMLElement>? = null,
content: ContentBuilder<HTMLElement>? = null
) = TagElement(
elementBuilder = CdsElementBuilder("cds-button"),
applyAttrs = {
if (attrs != null) apply(attrs)
attr("status", status.attributeValue)
},
content = content
)
/**
* This is a copy of the private class org.jetbrains.compose.web.dom.ElementBuilderImplementation
*/
internal class CdsElementBuilder<TElement : Element>(private val tagName: String) : ElementBuilder<TElement> {
private val element: Element by lazy {
document.createElement(tagName)
}
override fun create(): TElement = element.cloneNode() as TElement
}
sealed interface CdsButtonStatus {
object Primary : CdsButtonStatus
...
}
internal val CdsButtonStatus.attributeValue
get() = when (this) {
CdsButtonStatus.Primary -> "primary"
...
}
Make us of your #Composable!
fun main() {
...
renderComposable(rootElementId = "root") {
CdsButton(
status = CdsButtonStatus.Success
) {
Text("It works! :-)")
}
}
}
For reasons that have to do with Jetpack Compose input modifiers consuming all MotionEvents, I find myself writing my own scroll routine for a Composable, of which I have access to the ScrollState.
I have figured out everything I need except flinging. I can't see how to apply performFling(initialVelocity) on a ScrollState. All I can find in the docs are ScrollState.scrollTo and ScrollState.scrollBy, which aren't so useful with flings, since the scroll destination or size is unknown.
I also can't find a ScrollState listener, similar to onScrollStateChanged(state: Int) in the old Android world, that fires when scrolling state changes.
Here is what I have in case somebody can point me in the right direction:
var lastY: Float? = null
var velocityTracker: VelocityTracker? = null
fun scroll(event: MotionEvent) {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
velocityTracker = VelocityTracker.obtain()
lastY = event.y
}
MotionEvent.ACTION_UP -> {
lastY = null
velocityTracker?.let {
it.computeCurrentVelocity(1000)
val initialVelocity = it.yVelocity
velocityTracker?.recycle()
coroutineScope.launch {
???? scrollState.PERFORMFLING?(initialVelocity) ????
AND THEN WHEN THE FLING IS FINISHED viewModel.scrollOffset = scrollState.value
}
}
}
else -> {
velocityTracker?.addMovement(event)
lastY?.let {
val scrollAmount = it - event.y
lastY = event.y
coroutineScope.launch {
scrollState.scrollBy(scrollAmount)
viewModel.scrollOffset = scrollState.value
}
}
}
}
}
You could try using a nestedScroll:
val nestedScrollConnection = remember {
object : NestedScrollConnection {
override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
return super.onPostFling(consumed, available)
}
override suspend fun onPreFling(available: Velocity): Velocity {
return super.onPreFling(available)
}
}
}
Column(modifier = Modifier
.verticalScroll(rememberScrollState())
.nestedScroll(nestedScrollConnection)) {
}
If this doesn't work, just search
cs.android.com
for theNestedScrollConnection and it should give you a hint on how to handle flinging in Compose. Maybe NestedScrollConnection is all you need since it provides support for scrolling as well. You probably can ditch your code and just use NestedScrollConnection. To see NestedScrollConnection in action, check out the demo:
https://github.com/JohannBlake/Jetmagic
How can I get a reference to the first tab? And furthermore how do I get its Stage?
class MainApp : App() {
override val primaryView = MainView::class
class MainView : View() {
override val root = VBox()
init {
with(root) {
tabpane {
tab("Report") {
hbox {
// TODO Want a reference to this tab here.
// Ideally something like tab.getStage()
this += Button("Hello 1")
}
}
tab("Data Entry") {
hbox {
this += Button("Hello 2")
}
}
}
}
}
}
}
Quickly: I've seen a lot of your posts here and they're pretty basic questions. These are things you could figure out on your own if you did your own digging. I'd recommend at least looking at the official guide to get a good grasp on most of what you need to know. Then, check out other posts on here to see if they've been answered already.
But to answer your question:
class MainView : View() {
override val root = vbox {
tabpane {
tab("Report") {
hbox {
val tab = this#tab //Here is your tab
button("Hello 1")
}
}
tab("Data Entry") {
hbox {
button("Hello 2")
}
}
}
}
}
Again, I would urge you to look at the guide, as you missed some helpful building tools (see how I built the buttons? see how I moved the root out of init?). I'd hate for you to code more than you need to then realize you could've done less work if you had known how.
Also: Tabs don't have references to stages. They just inherit Styleable and EventTarget, they're not like Views or Fragments.
I am looking for a way to include text in the footer of all Dokka generated docs. I am not seeing this option being advertised by the Gradle or Maven plugins for Dokka.
Is this possible? Can you point me to a sample?
You can set your own footer by overriding the Dokka footer message.
{module}/build.gradle
tasks.named("dokkaHtml").configure {
pluginsMapConfiguration.set(
[
"org.jetbrains.dokka.base.DokkaBase": """{
"footerMessage": "Your New Footer!"
}"""
]
)
}
This will replace the Copyright 20xx in the current footer.
For further details on multi-module / css support, recommend checking out the source below.
Source: Raywenderlich
There are two instance methods in dokka package – one for footer, one for header:
fun appendFooter(to:) { }
fun appendHeader(to:, title:, basePath:) { }
Here's a real code how it looks like:
package org.jetbrains.dokka
import java.io.File
interface HtmlTemplateService {
fun appendHeader(to: StringBuilder, title: String?, basePath: File)
fun appendFooter(to: StringBuilder)
companion object {
fun default(css: String? = null): HtmlTemplateService {
return object : HtmlTemplateService {
override fun appendFooter(to: StringBuilder) {
if (!to.endsWith('\n')) {
to.append('\n')
}
to.appendln("</BODY>")
to.appendln("</HTML>")
}
override fun appendHeader(to: StringBuilder, title: String?, basePath: File) {
to.appendln("<HTML>")
to.appendln("<HEAD>")
to.appendln("<meta charset=\"UTF-8\">")
if (title != null) {
to.appendln("<title>$title</title>")
}
if (css != null) {
val cssPath = basePath.resolve(css)
to.appendln("<link rel=\"stylesheet\" href=\"$cssPath\">")
}
to.appendln("</HEAD>")
to.appendln("<BODY>")
}
}
}
}
}
I think it must be working even in dokka.playground.
Hope this helps.