I'm trying to combine UserAwareAction with Cache. I'm able to get CacheBeforeSilhouette but not the other way around? Could anyone give me a hint on how to do this?
#Singleton
class MessageController #Inject() (
implicit val env: Environment[DefaultEnv],
silhouette: Silhouette[DefaultEnv],
cache: CacheApi,
cached: play.api.cache.Cached)
extends Controller with I18nSupport {
...
def testOnlySilhouette = silhouette.UserAwareAction { request =>
Ok("hi")
}
def testOnlyCache = cached("homePage") {
Action {
Ok("Hello world")
}
}
def testCacheOfSilhouette = cached("homePage") {
silhouette.UserAwareAction { request =>
Ok("hi")
}
}
def testSilhouetteOfCache =
silhouette.UserAwareAction { request =>
cached("homePage") {
val res:Result = Ok("hi")
res //type mismatch; found : play.api.mvc.Result required: play.api.mvc.EssentialAction
}
}
Christian Kaps #akkie replied on gitter:
The problem is first that the play.api.cache.Cached.applyfunction returns a CachedBuilderwhich needs an EssentialAction as parameter. So you must always write:
Cached("some-key") {
Action {
Ok("")
}
}
The second problem ist that the request types are not compatible. The Play classes needs a Request[B] but Silhouette produces a SecuredRequest[E, B]. With a bit handwork you can get it to work:
def testSilhouetteOfCache =
silhouette.SecuredAction.async { securedRequest =>
cached("some-key") {
Action {
Ok("")
}
}.apply(securedRequest.request).run()
}
You need a implicit val materializer: Materializer injected
Related
The Item.kt class is
#Entity(tableName = "item")
class Item(
val id: Long,
val title: String,
) {
#Ignore
var selection: Boolean = false
}
Then i make a query to get all the items in the table ,it return
LiveData<List<Item>>
Then in the viewModel i want to apply selection(true) accordig to the Mutablelivedata selectionId, the selection id contain MutableLiveData<Long> (it contain an id in the LiveData<List<Item>>)
The MyViewModel.kt code is look like this
class MyViewModel(val repository: Repository) : ViewModel() {
..........
......
val selectionId: MutableLiveData<Long> by lazy {
MutableLiveData<Long>()
}
fun setSelectionId(id: Long) {
selectionId.postValue(id)
}
..........
......
val itemLiveList: LiveData<List<Item>> = liveData(Dispatchers.IO) {
emitSource(repository.getItems())
}
}
If it is an List<Item> i can do somethig like this
val ItemWithSelection: List<Item> = repository.getItems().apply {
this.forEach {
if (it.id == selectionId) {
it.selection = true
}
}
}
but i don't know how to achieve this using Mediator LiveData . Please help me
I don't understand everything in your code, for example I have never seen a function called liveData(CoroutineDispatcher). But do you mean you want something like this?
val listWithoutSelection = liveData(Dispatchers.IO) {
emitSource(repository.getItems())
}
val listWithSelection = MediatorLiveData<List<Item>>().apply {
addSource(listWithoutSelection) { updateListSelection() }
addSource(selectionId) { updateListSelection() }
}
fun updateListSelection() {
listWithSelection.value = listWithoutSelection.value?.map {
if (it.id == selectionId.value)
it.copyWithSelection(true)
else
it
}
}
The copyWithSelection could be easily done with Kotlin data classes. It is not needed dependent on whether you want to modify the object you get from the database. If you only use that object here, you could just always reset the selection of the others to false and then you can keep the object and you don't need a copy.
If you know Google's experimental Android Architecture Components, you probably know MutableLiveData. Trying to make it a bit more fun to use I came with:
class KotlinLiveData<T>(val default: T) {
val data = MutableLiveData<T>()
operator fun getValue(thisRef: Any?, property: KProperty<*>):T {
return data.value ?: default
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value:T) {
if (Looper.myLooper() == Looper.getMainLooper()) {
data.value = value
} else {
data.postValue(value)
}
}
}
And then I can:
var name : String by KotlinLiveData("not given")
name = "Chrzęszczybrzęczykiewicz"
But alas - that makes data which is needed i.e. to register Observer inaccessible:
name.data.observe(this, nameObserver) // won't work :(
Any idea if I can get it somehow?
You can access the delegate object of the property and get the MutableLiveData<T> from it:
inline fun <reified R> KProperty<*>.delegateAs<R>(): R? {
isAccessible = true
return getDelegate() as? R
}
Then the usage is:
::name.delegateAs<KotlinLiveData<String>>?.data?.observe(this, nameObserver)
To reference a member property, use this::name or someInstance::name.
This solution requires you to add the Kotlin reflection API, kotlin-reflect, as a dependency to your project. Also, due to the type erasure, the .delegateAs<KotlinLiveData<String>> call is not type-safe: it can only check that the delegate is KotlinLiveData<*> but not that its type argument is String.
Thanks to hotkey's solution, here's some better code:
class KotlinLiveData<T>(val default: T, val liveData : MutableLiveData<T>? = null) {
val data = liveData ?: MutableLiveData<T>()
operator fun getValue(thisRef: Any?, property: KProperty<*>):T {
return data.value ?: default
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value:T) {
if (Looper.myLooper() == Looper.getMainLooper()) {
data.value = value
} else {
data.postValue(value)
}
}
}
inline fun <reified R> KMutableProperty0<*>.getLiveData(): MutableLiveData<R> {
isAccessible = true
return (getDelegate() as KotlinLiveData<R>).data
}
inline fun <reified R> KMutableProperty0<*>.observe(owner: LifecycleOwner, obs : Observer<R>) {
isAccessible = true
(getDelegate() as KotlinLiveData<R>).data.observe(owner,obs)
}
Now I can:
someViewModel::name.observe(myActivity, Observer<String>{...})
with
someViewModel.name = "Kowalski, Leon"
working as expected
This class enables using LiveData with Android Data Binding out of the box.
the simplest way you can achieve is make the delegator to a field, for example:
#JvmField val dataOfName = KotlinLiveData("not given")
var name : String by dataOfName
then you can using live data in the class, for example:
dataOfName.data.observe(this, nameObserver)
name = "Chrzęszczybrzęczykiewicz"
OR you can write some syntax suglar, for example:
var name : String by live("not given").observe(this, nameObserver)
Note you can make nameObserver lazily too, for example:
val observers by lazy{mutableListOf<Observer>()}
var name : String by live("not given").observe(this){data->
observers.forEach{it.dataChanged(data)}
}
then you can do something like as below:
observers+= nameObserver;
name = "Chrzęszczybrzęczykiewicz"
observers-= nameObserver;
Is it possible to call methodTwo() as in
def map = [methodOne: { return "${methodTwo()}" }, methodTwo: { return "Hey" }] as MyInterface
methodTwo cannot be found at runtime by groovy (it seems that it is searching its definition inside the class where the map was defined)
You can call the method declaring the map variable before, and then referencing it:
interface MyInterface {
def methodOne()
def methodTwo()
}
def map
map = [
methodOne: { return "${map.methodTwo()}" },
methodTwo: { return "Hey" }
] as MyInterface
assert map.methodOne() == "Hey"
I just tried out the grails feeds-plugin http://grails.org/Feeds+Plugin
It works very easy and well, but there´s one thing I don´t understand.
As soon as I add a publishedDate, like:
reviews.each() {
review -> entry('fooTitle'){
publishedDate = review.dtCreated
review.rating + ' ' + review.comment
}
}
It generates two tags:
<pubDate>Tue, 04 Sep 2012 12:10:02 GMT</pubDate>
<dc:date>2012-09-04T12:10:02Z</dc:date>
The entry in Database is:
"dtCreated": ISODate("2013-01-15T00:52:47.0Z"),
but I only want the <pubDate> to be generated, because feed validator throws this error:
An item should not include both pubDate and dc:date
How can I solve this? I would love to use this plugin but I need a valid RSS.
If you don't mind a quick and dirty solution you can try this (it's taken from plugin's integration tests and modified):
import groovy.xml.XmlUtil
import groovy.xml.StreamingMarkupBuilder
import feedsplugin.FeedBuilder
import com.sun.syndication.io.SyndFeedOutput
class TestController {
private renderToString(feedType, feedVersion, Closure closure) {
def builder = new FeedBuilder()
builder.feed(closure)
def type = feedType
def version = feedVersion
SyndFeedOutput output = new SyndFeedOutput()
def sw = new StringWriter()
output.output(builder.makeFeed(type, version),sw)
sw.toString()
}
private removeDcDate(String rssFeed) {
def dom = new XmlSlurper().parseText(rssFeed)
dom.channel[0].item.eachWithIndex { item, i ->
dom.channel[0].item[i].date = {}
}
def newResp = XmlUtil.serialize(new StreamingMarkupBuilder().bind {
mkp.declareNamespace (rdf:"http://www.w3.org/1999/02/22-rdf-syntax-ns#")
mkp.declareNamespace (dc:"http://purl.org/dc/elements/1.1/")
mkp.declareNamespace (content:"http://purl.org/rss/1.0/modules/content/")
mkp.declareNamespace (itunes:"http://www.itunes.com/dtds/podcast-1.0.dtd")
mkp.declareNamespace (taxo:"http://purl.org/rss/1.0/modules/taxonomy/")
mkp.yield dom
})
newResp
}
def test = {
def articles = ['A', 'B', 'C']
def outStr= renderToString("rss", "2.0") {
title = 'Test feed'
link = 'http://somewhere.com/'
description = "This is a test feed"
articles.each() { article ->
entry("Title for \$article") {
content(type:'text/html') {
return "Content for \$article"
}
link = 'http://somewhere.com/x'
publishedDate = new Date()
}
}
}
def cleanedFeed = removeDcDate(outStr)
render text: cleanedFeed, contentType: "application/rss+xml", encoding: "UTF-8"
}
}
Basically what it does is calling feedbuilder directly to get a string representation of the feed, then it get parsed it and the "dc:date" tags are deleted. The result is rendered as usual.
I've tested this workaround inside the plugin test suite with grails 1.3.7
I'm trying to alter JSON responses in my Grails 1.3.7 application using filters, but they're not working the way I expect. What I would like to do is use something like render myobject as JSON in my action and then in the filter do something like this:
jsonPostProcess(controller:'json', action:'*') {
after = {
if (myCustomLogicHere) {
return false // render empty string
} else if (more logic) {
// write something else to the response
}
}
}
What actually happens is the response is sent back before the after block is executed. The same goes for afterView.
Is there a way to accomplish what I'm trying to do the Grails way?
Return myobject from the controller, then call render from the filter, e.g.:
class JsonController {
someAction = {
...
myobject
}
}
and
jsonPostProcess(controller:'json', action:'*') {
after = {
myobject ->
if (myCustomLogicHere) {
return false // render empty string
} else if (more logic) {
// write something else to the response
}
render myobject as JSON
}
}
#JonoB answer is almost right:
Return myobject from the controller, then call render from the filter, e.g.:
class JsonController {
someAction = {
//...some logic
return [myobject: myobject]
}
}
and in filter
jsonPostProcess(controller:'json', action:'*') {
after = {
Map model ->
//... my custom logic here
render model.myobject as JSON
return false
}
}