Aaron Cohn
Aaron Cohn

Reputation: 941

How to use my own Iterable in Play Framework template

Problem in a Nutshell

I cannot iterate over a lightly wrapped collection in a Play! Framework template. I made the assumption that simply implementing the Iterable interface would enable me to use for-each loops in the templates, but that seems to be incorrect.

How can I get this working?

What I Did

I created a simple wrapper class around java.util.Queue. I made the assumption that implementing Iterable would allow me to use a for-each loop in a Play! Framework template.

public class DecisionQueue implements Iterable<Decision> {
    Queue<Decision> decisions;

    public DecisionQueue() {
        decisions = new LinkedList<Decision>();
    }

    // redacted methods for manipulating the queue

    @Override
    public Iterator<Decision> iterator() {
        return decisions.iterator();
    }
}

I provided an instance of the wrapper to a template.

public static Result getFormOutput() {
    DecisionQueue decisionQueue = getDecisionQueue();

    return ok(views.html.questionnaire.output.render(decisionQueue));
}

I attempted to iterate over the wrapper in my template.

@(decisionQueue: data.DecisionQueue)

<ul>
@for(decision <- decisionQueue) // Problem here
  // redacted
}
</ul>

I got the following stack trace during compilation.

[error] C:\...\app\views\questionnaire\output.scala.html:12: type mismatch;
[error]  found   : decisionQueue.type (with underlying type models.data.DecisionQueue)
[error]  required: ?{def map(x$1: ? >: <error> => play.twirl.api.HtmlFormat.Appendable): ?}
[error]     (which expands to)  ?{def map(x$1: ? >: <error> => play.twirl.api.Html): ?}
[error] Note that implicit conversions are not applicable because they are ambiguous:
[error]  both method javaCollectionToScala in object TemplateMagic of type [T](x: Iterable[T])Iterable[T]
[error]  and method iterableAsScalaIterable in trait WrapAsScala of type [A](i: Iterable[A])Iterable[A]
[error]  are possible conversion functions from decisionQueue.type to ?{def map(x$1: ? >: <error> => play.twirl.api.HtmlFormat.Appendable): ?}
[error] @for(decision <- decisionQueue) {
[error]                             ^
[error] one error found
[error] (compile:compile) Compilation failed

Workaround

It works if I pass the underlying Queue directly to the template instead of using the wrapper.

Upvotes: 1

Views: 1061

Answers (2)

Raphael M&#228;der
Raphael M&#228;der

Reputation: 838

Just had the exact same problem with Play 2.5.14 and fixed it by making the conversion explicit by adding .asScala. Thought I'd share my solution even though the question's old, since it shows up when googling play iterable implicit conversion ambiguous at first position.

Before

@(subscriptions: java.lang.Iterable[Subscription])
@if(subscriptions.nonEmpty) {
  ...
}

Gave me this error:

[error] /some/path/accountDetails.scala.html:31: type mismatch;
[error]  found   : subscriptions.type (with underlying type Iterable[some.package.Subscription])
[error]  required: ?{def nonEmpty: ?}
[error] Note that implicit conversions are not applicable because they are ambiguous:
[error]  both method javaCollectionToScala in object TemplateMagic of type [T](x: Iterable[T])Iterable[T]
[error]  and method iterableAsScalaIterable in trait WrapAsScala of type [A](i: Iterable[A])Iterable[A]
[error]  are possible conversion functions from subscriptions.type to ?{def nonEmpty: ?}
[error]         @if(subscriptions.nonEmpty) {
[error]             ^

After

@(subscriptions: java.lang.Iterable[Subscription])
@if(subscriptions.asScala.nonEmpty) {
  ...
}

Works out of the box for me, no additional imports required.

Hope it helps.

Upvotes: 0

Reid Spencer
Reid Spencer

Reputation: 2785

You are mixing languages here. Your DecisionQueue class is written in Java while Twirl templates compile to Scala. The languages are compatible but not transparently. You are trying to iterate in Scala over a Java collection (LinkedList). Scala doesn't know how to do that without some help and the error is telling you that it found some ambiguity with implicit functions that it tried to use to convert the Java collection. You might want to help it along a little by importing the converters and using them, like this:

@(decisionQueue: data.DecisionQueue)
import scala.collection.JavaConversions._
<ul>
@for(decision <- decisionQueue.iterator) // Problem here
  // redacted
}
</ul>

Upvotes: 1

Related Questions