Search code examples
scalaakkaakka-streamakka-http

Could not find implicit value for ActorSystem


I'm just starting out with Akka and Scala and I'm trying to connect to a WebSocket using Akka Streams. I've created my SocketActor below and I try to instantiate from the main method.

Here is my SocketActor:

package com.lightbend.akka.sample

import akka.actor.{Actor, Props}
import akka.Done
import akka.http.scaladsl.Http
import akka.stream.scaladsl._
import akka.http.scaladsl.model.ws._
import scala.concurrent.Future


object SocketActor {

  def props(coinApiIdentifier: String): Props = Props(new SocketActor(coinApiIdentifier))

  case object Start

  case object Stop

}

class SocketActor(val ticker: String) extends Actor {

  import SocketActor._


  // Future[Done] is the materialized value of Sink.foreach,
  // emitted when the stream completes
  private val incoming: Sink[Message, Future[Done]] =
  Sink.foreach[Message] {
    case message: TextMessage.Strict =>
      println(message.text)
  }

  // send this as a message over the WebSocket
  private val outgoing = Source.single(TextMessage("hello world!"))

  // flow to use (note: not re-usable!)
  private val webSocketFlow = Http().webSocketClientFlow(WebSocketRequest("wss://api.com/v1/"))

  // the materialized value is a tuple with
  // upgradeResponse is a Future[WebSocketUpgradeResponse] that
  // completes or fails when the connection succeeds or fails
  // and closed is a Future[Done] with the stream completion from the incoming sink
  private val graph =
  outgoing
    .viaMat(webSocketFlow)(Keep.right) // keep the materialized Future[WebSocketUpgradeResponse]
    .toMat(incoming)(Keep.both) // also keep the Future[Done]


  override def receive: PartialFunction[Any, Unit] = {
    case Start =>
      println("Start message received.")
      graph.run()
  }
}

And my main method:

object AkkaQuickstart extends App {

  // Create the 'helloAkka' actor system
  val system: ActorSystem = ActorSystem("test")
  val materializer: ActorMaterializer = ActorMaterializer()

  val socketActor: ActorRef =
    system.actorOf(SocketActor.props("hello"), "socket-actor")

  socketActor ! Start
}

Unfortunately, I get the error:

Error:(38, 35) could not find implicit value for parameter system: akka.actor.ActorSystem private val webSocketFlow = Http().webSocketClientFlow(WebSocketRequest("wss://api.com/v1/"))

I've tried passing some implicit parameters to the constructor of the SocketActor but that didn't work too well. It seems like the ActorSystem is not in scope for some reason. How can I get my system in scope for the Http() function in SocketActor?


Solution

  • Define an implicit val:

    class SocketActor(val ticker: String) extends Actor {
      implicit val sys = context.system
      // ...
    }
    

    This will provide the implicit ActorSystem that the Http object expects.

    There is another issue with your code: the stream in your actor will not run because there is no materializer in scope. One way to address this is to create the materializer inside the actor:

    class SocketActor(val ticker: String) extends Actor {
      implicit val sys = context.system
      implicit val mat = ActorMaterializer()(context)
      // ...
    }
    

    Note that if the materializer were defined as implicit val mat = ActorMaterializer(), it would implicitly use context.system because of the implicit val sys = context.system. Instead, the materializer is created with the actor's context explicitly. This is done because of the warning in the documentation:

    Do not create new actor materializers inside actors by passing the context.system to it. This will cause a new ActorMaterializer to be created and potentially leaked (unless you shut it down explicitly) for each such actor. It is instead recommended to either pass-in the Materializer or create one using the actor’s context.

    The recommended approach, which allows the creator of the actor to reuse a materializer, is to pass the materializer to the actor as an implicit parameter:

    class SocketActor(val ticker: String)(implicit val mat: ActorMaterializer) extends Actor {
      implicit val sys = context.system
      // ...
    }
    

    Then you could pass the materializer in the main program to this actor:

    object AkkaQuickstart extends App {
    
      implicit val system: ActorSystem = ActorSystem("test")
      implicit val materializer: ActorMaterializer = ActorMaterializer()
    
      val socketActor: ActorRef =
        system.actorOf(Props(classOf[SocketActor], "hello", materializer), "socket-actor")
    
      socketActor ! Start
    }