ReturnValueHandler class abstract interface

Strategy interface for handling the return value of a JetLeaf controller method invocation.

A ReturnValueHandler interprets the value returned by a controller method and performs a corresponding action — such as:

  • Rendering a view
  • Writing text or binary data to the HTTP response
  • Serializing an object to JSON or another format
  • Setting response headers and status codes
  • Handling redirects and forward operations

This abstraction enables flexible return value handling and allows the framework to support multiple response paradigms (MVC, REST, etc.) without coupling controller methods to low-level HTTP operations.

Resolution Process

When a controller method completes execution, the framework:

  1. Iterates through registered handlers in order
  2. Calls canHandle to find a compatible handler
  3. Invokes handleReturnValue on the first matching handler
  4. Stops processing after the first successful resolution

Responsibilities

  • Determine if the handler can handle a given return type or value via canHandle
  • Process and render the value appropriately via handleReturnValue
  • Set appropriate HTTP response headers and status codes
  • Handle any rendering errors gracefully

Built-in Handler Implementations

JetLeaf provides several standard handlers:

  • ViewNameReturnValueHandler → Handles string return values as view names
  • PageViewReturnValueHandler → Processes PageView instances
  • ResponseBodyReturnValueHandler → Handles ResponseBody wrapper objects
  • JsonReturnValueHandler → Serializes objects to JSON with proper content type
  • StringReturnValueHandler → Writes plain text responses
  • VoidReturnValueHandler → Handles methods that return void or null
  • RedirectReturnValueHandler → Processes redirect instructions

Example: Custom JSON Handler

class JsonReturnValueHandler implements ReturnValueHandler {
  final JsonEncoder _encoder = JsonEncoder();

  @override
  bool canHandle(Method method, Object? returnValue, ServerHttpRequest req) {
    // Handle any non-primitive object that isn't a framework type
    return returnValue != null &&
           !_isFrameworkType(returnValue) &&
           !_isPrimitive(returnValue);
  }

  @override
  Future<void> handleReturnValue(
    Object? returnValue,
    Method method,
    ServerHttpRequest req,
    ServerHttpResponse res,
    HandlerMethod handler,
  ) async {
    // Set JSON content type
    res.headers.setContentType(MediaType.APPLICATION_JSON);
    
    // Serialize and write response
    final jsonString = _encoder.encode(returnValue);
    await res.body.writeString(jsonString, Closeable.DEFAULT_ENCODING);
  }

  bool _isFrameworkType(Object value) {
    return value is PageView ||
           value is ResponseBody ||
           value is View;
  }

  bool _isPrimitive(Object value) {
    return value is String ||
           value is num ||
           value is bool ||
           value is List ||
           value is Map;
  }
}

Example: File Download Handler

class FileDownloadReturnValueHandler implements ReturnValueHandler {
  @override
  bool canHandle(Method method, Object? returnValue, ServerHttpRequest req) {
    return returnValue is FileDownload;
  }

  @override
  Future<void> handleReturnValue(
    Object? returnValue,
    Method method,
    ServerHttpRequest req,
    ServerHttpResponse res,
    HandlerMethod handler,
  ) async {
    final download = returnValue as FileDownload;
    
    // Set download headers
    res.headers
      ..setContentType(download.contentType)
      ..set('Content-Disposition', 'attachment; filename="${download.filename}"')
      ..setContentLength(download.contentLength);
    
    // Stream file content
    await download.content.pipe(res.body);
  }
}

Framework Integration

  • Handlers are registered in the HandlerMethodAdapter configuration
  • Execution order is determined by registration order
  • Custom handlers can be added to extend framework capabilities
  • Handlers are typically stateless and thread-safe

Design Notes

  • This interface is part of the internal controller resolution system
  • Each handler must be stateless and reusable across requests
  • Resolution order is managed by JetLeaf's HandlerMethodAdapter
  • Implementations should avoid blocking operations during rendering
  • Handlers should handle errors gracefully and provide meaningful feedback

Best Practices

  • Make canHandle checks as efficient as possible
  • Handle null return values appropriately
  • Set proper HTTP headers and status codes
  • Use async operations for I/O-intensive tasks
  • Provide clear error messages for debugging
  • Consider content negotiation when applicable
Implementers
Annotations
  • @Author.new("Evaristus Adimonyemma")

Properties

hashCode int
The hash code for this object.
no setterinherited
runtimeType Type
A representation of the runtime type of the object.
no setterinherited

Methods

canHandle(Method? method, Object? returnValue, ServerHttpRequest request) bool
Determines whether this handler can handle the given return value.
equalizedProperties() List<Object?>
Mixin-style contract for value-based equality, hashCode, and toString.
inherited
getSupportedMediaTypes() List<MediaType>
Returns the list of media types this handler supports by default.
handleReturnValue(Object? returnValue, Method? method, ServerHttpRequest request, ServerHttpResponse response, HandlerMethod? hm) Future<void>
Processes the return value and writes the appropriate response.
noSuchMethod(Invocation invocation) → dynamic
Invoked when a nonexistent method or property is accessed.
inherited
toString() String
A string representation of this object.
inherited

Operators

operator ==(Object other) bool
The equality operator.
inherited