Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Todo App

Creating a Todo application that leverages Scala's Futures and Promises involves handling tasks asynchronously. This is particularly useful for operations that might take a long time, such as database interactions. In this simplified example, we'll implement a basic Todo app with asynchronous add and get functionalities.

Step 1: Setting Up the Environment

First, ensure you have the Scala execution context in scope for running your futures:

import scala.concurrent.{Future, ExecutionContext, Promise}
import scala.util.{Success, Failure}
import scala.concurrent.ExecutionContext.Implicits.global

Step 2: Define the Todo Model and Storage

Define a simple Todo model and a mock asynchronous storage for todos. In a real application, this storage might interact with a database.

case class Todo(id: Int, task: String, isCompleted: Boolean)

object TodoStorage {
  private var todos: List[Todo] = List()

  // Asynchronous add method
  def add(todo: Todo): Future[Unit] = Future {
    Thread.sleep(1000) // Simulate database delay
    todos = todo :: todos
  }

  // Asynchronous get method
  def get(id: Int): Future[Option[Todo]] = Future {
    Thread.sleep(1000) // Simulate database delay
    todos.find(_.id == id)
  }
}

Step 3: Implement the TodoService

The TodoService will interact with the TodoStorage. It includes error handling and demonstrates how to use a Promise to create a custom future that might be completed outside the usual flow.

object TodoService {
  def addTask(task: String): Future[Int] = {
    val todo = Todo(
      id = TodoStorage.todos.length + 1, // Simple ID generation strategy
      task = task,
      isCompleted = false
    )

    for {
      _ <- TodoStorage.add(todo)
    } yield todo.id
  }

  def completeTask(id: Int): Future[Boolean] = {
    val promise = Promise[Boolean]()

    TodoStorage.get(id).onComplete {
      case Success(Some(todo)) =>
        TodoStorage.add(todo.copy(isCompleted = true)).onComplete {
          case Success(_) => promise.success(true)
          case Failure(_) => promise.failure(new Exception("Failed to update the todo."))
        }
      case Success(None) => promise.failure(new NoSuchElementException("Todo not found."))
      case Failure(exception) => promise.failure(exception)
    }

    promise.future
  }
}

Step 4: Usage Example

Here’s how you might use TodoService to add and complete tasks:

val added = TodoService.addTask("Learn Scala Futures and Promises")

added.onComplete {
  case Success(id) =>
    println(s"Added todo with id: $id")
    TodoService.completeTask(id).onComplete {
      case Success(_) => println(s"Todo $id completed.")
      case Failure(exception) => println(s"Error completing todo: ${exception.getMessage}")
    }
  case Failure(exception) => println(s"Error adding todo: ${exception.getMessage}")
}