diff --git a/Examples/xx-messenger/Sources/GroupsFeature/GroupsComponent.swift b/Examples/xx-messenger/Sources/GroupsFeature/GroupsComponent.swift
index ae533463a649f3c5cf6e72be53d6462ed0a21f66..9d12a95d49af444dc165f3b92ddbc6838ffb0ca0 100644
--- a/Examples/xx-messenger/Sources/GroupsFeature/GroupsComponent.swift
+++ b/Examples/xx-messenger/Sources/GroupsFeature/GroupsComponent.swift
@@ -1,20 +1,49 @@
+import AppCore
 import ComposableArchitecture
+import Foundation
+import XXModels
 
 public struct GroupsComponent: ReducerProtocol {
   public struct State: Equatable {
-    public init() {}
+    public init(
+      groups: IdentifiedArrayOf<Group> = []
+    ) {
+      self.groups = groups
+    }
+
+    public var groups: IdentifiedArrayOf<XXModels.Group> = []
   }
 
   public enum Action: Equatable {
     case start
+    case didFetchGroups([XXModels.Group])
+    case didSelectGroup(XXModels.Group)
   }
 
   public init() {}
 
+  @Dependency(\.app.dbManager.getDB) var db: DBManagerGetDB
+  @Dependency(\.app.mainQueue) var mainQueue: AnySchedulerOf<DispatchQueue>
+  @Dependency(\.app.bgQueue) var bgQueue: AnySchedulerOf<DispatchQueue>
+
   public var body: some ReducerProtocol<State, Action> {
     Reduce { state, action in
       switch action {
       case .start:
+        return Effect
+          .catching { try db() }
+          .flatMap { $0.fetchGroupsPublisher.callAsFunction(.init()) }
+          .assertNoFailure()
+          .map(Action.didFetchGroups)
+          .subscribe(on: bgQueue)
+          .receive(on: mainQueue)
+          .eraseToEffect()
+
+      case .didFetchGroups(let groups):
+        state.groups = IdentifiedArray(uniqueElements: groups)
+        return .none
+
+      case .didSelectGroup(_):
         return .none
       }
     }
diff --git a/Examples/xx-messenger/Sources/GroupsFeature/GroupsView.swift b/Examples/xx-messenger/Sources/GroupsFeature/GroupsView.swift
index 38cbefbe59da5e22b68830415e89ad7f3724c519..79e102148377c3f30234df5ff32e9fd021f5d2ce 100644
--- a/Examples/xx-messenger/Sources/GroupsFeature/GroupsView.swift
+++ b/Examples/xx-messenger/Sources/GroupsFeature/GroupsView.swift
@@ -1,5 +1,7 @@
+import AppCore
 import ComposableArchitecture
 import SwiftUI
+import XXModels
 
 public struct GroupsView: View {
   public typealias Component = GroupsComponent
@@ -12,17 +14,43 @@ public struct GroupsView: View {
 
   struct ViewState: Equatable {
     init(state: Component.State) {}
+
+    var groups: IdentifiedArrayOf<XXModels.Group> = []
   }
 
   public var body: some View {
     WithViewStore(store, observe: ViewState.init) { viewStore in
       Form {
-
+        ForEach(viewStore.groups) { group in
+          groupView(group) {
+            viewStore.send(.didSelectGroup(group))
+          }
+        }
       }
       .navigationTitle("Groups")
       .task { viewStore.send(.start) }
     }
   }
+
+  func groupView(
+    _ group: XXModels.Group,
+    onSelect: @escaping () -> Void
+  ) -> some View {
+    Section {
+      Button {
+        onSelect()
+      } label: {
+        HStack {
+          Label(group.name, systemImage: "person.3")
+            .font(.callout)
+            .tint(Color.primary)
+          Spacer()
+          Image(systemName: "chevron.forward")
+        }
+        GroupAuthStatusView(group.authStatus)
+      }
+    }
+  }
 }
 
 #if DEBUG
diff --git a/Examples/xx-messenger/Tests/GroupsFeatureTests/GroupsComponentTests.swift b/Examples/xx-messenger/Tests/GroupsFeatureTests/GroupsComponentTests.swift
index be3456a9494aa0d1dc5677e27432f33516babcc8..c726955f4cc892ecffda8694a3a5bec90c54544d 100644
--- a/Examples/xx-messenger/Tests/GroupsFeatureTests/GroupsComponentTests.swift
+++ b/Examples/xx-messenger/Tests/GroupsFeatureTests/GroupsComponentTests.swift
@@ -1,14 +1,89 @@
+import Combine
 import ComposableArchitecture
+import CustomDump
 import XCTest
+import XXModels
 @testable import GroupsFeature
 
 final class GroupsComponentTests: XCTestCase {
+  enum Action: Equatable {
+    case didFetchGroups(XXModels.Group.Query)
+  }
+
+  var actions: [Action]!
+
+  override func setUp() {
+    actions = []
+  }
+
+  override func tearDown() {
+    actions = nil
+  }
+
   func testStart() {
+    let groupsSubject = PassthroughSubject<[XXModels.Group], Error>()
+
     let store = TestStore(
       initialState: GroupsComponent.State(),
       reducer: GroupsComponent()
     )
 
+    store.dependencies.app.mainQueue = .immediate
+    store.dependencies.app.bgQueue = .immediate
+    store.dependencies.app.dbManager.getDB.run = {
+      var db: Database = .unimplemented
+      db.fetchGroupsPublisher.run = { query in
+        self.actions.append(.didFetchGroups(query))
+        return groupsSubject.eraseToAnyPublisher()
+      }
+      return db
+    }
+
     store.send(.start)
+
+    XCTAssertNoDifference(actions, [
+      .didFetchGroups(.init())
+    ])
+
+    let groups: [XXModels.Group] = [
+      .stub(1),
+      .stub(2),
+      .stub(3),
+    ]
+    groupsSubject.send(groups)
+
+    store.receive(.didFetchGroups(groups)) {
+      $0.groups = IdentifiedArray(uniqueElements: groups)
+    }
+
+    groupsSubject.send(completion: .finished)
+  }
+
+  func testSelectGroup() {
+    let store = TestStore(
+      initialState: GroupsComponent.State(
+        groups: IdentifiedArray(uniqueElements: [
+          .stub(1),
+          .stub(2),
+          .stub(3),
+        ])
+      ),
+      reducer: GroupsComponent()
+    )
+
+    store.send(.didSelectGroup(.stub(2)))
+  }
+}
+
+private extension XXModels.Group {
+  static func stub(_ id: Int) -> XXModels.Group {
+    XXModels.Group(
+      id: "group-\(id)-id".data(using: .utf8)!,
+      name: "Group \(id)",
+      leaderId: "group-\(id)-leader-id".data(using: .utf8)!,
+      createdAt: Date(timeIntervalSince1970: TimeInterval(id * 86_400)),
+      authStatus: .participating,
+      serialized: "group-\(id)-serialized".data(using: .utf8)!
+    )
   }
 }