DEV Community

GoyesDev
GoyesDev

Posted on

[SUI] Inyectando objeto observable al ambiente

En lugar de inyectar una dependencia de un objeto a otro, en una jerarquía larga de componentes, se puede inyectar el modelo al ambiente ("environment") y luego leerlo de él.

Modelo de tipo Observable

En el caso de que el modelo de la aplicación sea de tipo Observable:

  1. Se debe retener la instancia a él por medio del property wrapper @State.
  2. Se debe inyectar la instancia al ambiente de la jerarquía de una vista (al que tienen acceso sus subvistas) con el modificador environment(_:).
  3. Luego, la vista que necesite hacer uso de aquel objeto Observable, tendrá que crear una propiedad con el "property wrapper" @Environment
@Observable
class Library {
  var books: [Book] = [Book(), Book(), Book()]

  var availableBooksCount: Int {
    books.filter(\.isAvailable).count
  }
}
Enter fullscreen mode Exit fullscreen mode
@main
struct BookReaderApp: App {
  @State private var library = Library()

  var body: some Scene {
    WindowGroup {
      LibraryView()
        .environment(library)
    }
  }
}
Enter fullscreen mode Exit fullscreen mode
struct LibraryView: View {
  @Environment(Library.self) private var library

  var body: some View {
      // ...
  }
}
Enter fullscreen mode Exit fullscreen mode

Si no se envuelve entre paréntesis el tipo de la propiedad Observable (en este caso, Library.self), habrá un error de compilación.

En caso de que sea posible que el objeto del ambiente no exista al momento de leer (en este caso, dentro de LibraryView), entonces se puede hacer que el tipo sea opcional, para que asigne nil en lugar de arrojar una excepción.

struct LibraryView: View {
  // ⚠️ Notar que aquí library es opcional
  @Environment(Library.self) private var library: Library?

  var body: some View {
      // ...
  }
}
Enter fullscreen mode Exit fullscreen mode

Inyectando al ambiente con un KeyPath

También se puede usar un keypath para inyectar el valor al ambiente, con environment(_:_:):

@main
struct BookReaderApp: App {
  @State private var x = Library()

  var body: some Scene {
    WindowGroup {
      LibraryView()
        .environment(\.ciertaBiblioteca, x)
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Luego, se puede usar el mismo keypath para leer el valor:

struct LibraryView: View {
  @Environment(\.ciertaBiblioteca) private var library

  var body: some View {
    // ...
  }
}
Enter fullscreen mode Exit fullscreen mode

Sin embargo, primero hay que crear la entrada en los EnvironmentValues:

extension EnvironmentValues {
  @Entry var ciertaBiblioteca: Library = Library()
}
Enter fullscreen mode Exit fullscreen mode

Creando un Binding al Environment de tipo Observable

Cuando se inyecta directamente un objeto de tipo Observable a una vista, se usa el "property wrapper" @Bindable para tener una referencia bidireccional. Al inyectarlo por medio de una variable de ambiente, ya se está usando el "property wrapper" @Environment. Por esta razón, hay que crear una nueva variable con el property wrapper @Bindable dentro del body haciendo referencia a la variable recibida con @Environment.

Por ejemplo:

@Observable
class Library {
  var books: [Book] = [Book(), Book(), Book()]
  // ⚠️ Se agregó esta variable para guardar el título
  var title: String = ""

  var availableBooksCount: Int {
    books.filter(\.isAvailable).count
  }
}
Enter fullscreen mode Exit fullscreen mode
struct LibraryView: View {
  @Environment(\.ciertaBiblioteca) private var library

  var body: some View {
    // ⚠️ Observar que el @Bindable no tiene que tener el mismo nombre que @Environment
    @Bindable var bindableLibrary = library
    Text("Mi biblioteca")
    TextField("Ingrese título", text: $bindableLibrary.title)
    // ...
  }
}
Enter fullscreen mode Exit fullscreen mode

Modelo de tipo ObservableObject

En el caso de que el modelo de la aplicación sea de tipo ObservableObject:

  1. Se debe retener la instancia a él por medio de los property wrapper @ObservedObject o @StateObject.
  2. Se debe inyectar la instancia al ambiente de la jerarquía de una vista (al que tienen acceso sus subvistas) con el modificador environmentObject(_:).
  3. Luego, la vista que necesite hacer uso de aquel objeto ObservableObject, tendrá que crear una propiedad con el "property wrapper" @EnvironmentObject
class Library: ObservableObject {
  @Published var books: [Book] = [Book(), Book(), Book()]

  var availableBooksCount: Int {
    books.filter(\.isAvailable).count
  }
}
Enter fullscreen mode Exit fullscreen mode
@main
struct BookReaderApp: App {
  @StateObject private var x = Library()

  var body: some Scene {
    WindowGroup {
      LibraryView()
        .environmentObject(x)
    }
  }
}
Enter fullscreen mode Exit fullscreen mode
struct LibraryView: View {
  @EnvironmentObject private var library: Library

  var body: some View {
      Text("Hola")
    // ...
  }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)