PUBLIC OBJECT

Factory Factory

I use lots of factory classes in my code.

But not everywhere. I don’t like the ceremony they require to create and use: why not a constructor? Factories don’t solve a business problem directly; they solve an architecture problem. They feel enterprisey and architecture astronauty.

/**
 * For example, a JSON adapter solves real
 * problems...
 */
interface JsonAdapter<T> {

  /** 
   * It can encode a OrderPizzaRequest into 
   * a JSON string. That's useful.
   */
  fun toJson(value: T): String

  /**
   * And it can decode another JSON string 
   * into an OrderPizzaResponse. Also handy.
   */
  fun fromJson(json: String): T

  /**
   * But what the heck is this thing? It's 
   * not serving a business purpose! It’s just
   * here to satisfy some fancy framework.
   */
  interface Factory {
    fun create(Type type): JsonAdapter?
  }
}

I feel conspicuous when I wear the architecture space suit! When I request a code review that uses Design Patterns, I’m compelled to explain myself: ‘I promise you there’s a business problem being solved in here...’

So I felt like I’d gone full fucking NASA recently when I found myself writing WidgetFactoryFactory. Writing is therapy so I’d like to explain what happened and also celebrate one neat trick I found along the way.

Widget

Redwood is a library we’re building to connect Compose to a design system. Describe a widget like this:

@Widget
data class Image(
  @Property val url: String,
)

and Redwood will generate an interface like this:

public interface Image<W : Any> : Widget<W> {
  public fun url(url: String): Unit
}

Next I implement that generated interface on each target platform, like UIKit or HTML. Finally I can write Compose code to target ’em all.

WidgetFactory

Redwood is a fancy framework and so it also generates a factory. This one’s handy: it gets the compiler to check that I support all the widgets on each platform.

interface EmojiSearchWidgetFactory<W : Any> : WidgetFactory {
  fun TextInput(): TextInput<W>
  fun Text(): Text<W>
  fun Image(): Image<W>
}

WidgetFactoryFactory

Redwood’s other magic trick is that it can split the business logic and display. It’s an age-old technique used by XWindows (1987) and XBox Cloud Gaming (2019). We’re using it to run a completely native UI in our mobile apps backed by non-native business logic. The non-native bit is Kotlin/JS today but we’re eager for Kotlin/Wasm.

To make all that work, we need to pass a JSON encoder to create the application’s WidgetFactory:

interface WidgetFactoryFactory {
  fun create(
    json: Json,
    protocolMismatchHandler: ProtocolMismatchHandler,
  ) : SplitWidgetFactory
}

When I type FactoryFactory I feel like my programming license is at risk of being revoked.

WidgetFactoryFactoryFactory

Redwood is an open source library that’s given us WidgetFactoryFactory and I’d like to put it to work in my company’s app.

Implement that interface above and I’m in business:

class CashAppWidgetFactoryFactory @Inject constructor(
  val imageLoader: ImageLoader,
) : WidgetFactoryFactory {
  override fun create(
    json: Json,
    protocolMismatchHandler: ProtocolMismatchHandler,
  ) = SplitWidgetFactory(
    CashAppWidgetFactory(imageLoader),
    json,
    protocolMismatchHandler,
  }
}

But oh no! To actually construct an ImageView for the ultimate Android widget, we need one of Android’s pesky Context objects. Oh well, we can solve this with AssistedInject:

class CashAppWidgetFactoryFactory @AssistedInject constructor(
  val imageLoader: ImageLoader,
  @Assisted private val context: Context,
) : WidgetFactoryFactory {
  override fun create(
    json: Json,
    protocolMismatchHandler: ProtocolMismatchHandler,
  ) = SplitWidgetFactory(
    CashAppWidgetFactory(context, imageLoader),
    json,
    protocolMismatchHandler,
  }
  
  @AssistedFactory
  interface Factory {
    fun create(context: Context): CashAppWidgetFactoryFactory
  }
}

This is fine.

Cheating My Way Out

I don’t want to go to programming jail. And yet, it’s going to be very difficult to prevent my teammates from seeing this declaration:

val cashAppWidgetFactoryFactoryFactory: CashAppWidgetFactoryFactory.Factory

I’m pleased with a trick-of-naming that I discovered way back in factory 2. It was never called WidgetFactoryFactory! Instead: WidgetSystem.

interface WidgetSystem {
  fun create(
    json: Json,
    protocolMismatchHandler: ProtocolMismatchHandler,
  ) : SplitWidgetFactory
}

This cascades through and the declaration above is reduced to this:

val cashAppWidgetSystemFactory: CashAppWidgetSystem.Factory

It’s not the most beautiful thing in the world, but it’s okay. A widget system actually gets close to that business value objective I started with: real people in the real world want their apps to have a good widget system. And what is a widget system but a maker of widgets?

If you find yourself inventing FooFactoryFactory, consider the name FooSystem instead.