Bird
Raised Fist0
Fluttermobile~20 mins

Repository pattern in Flutter - Mini App: Build & Ship

Choose your learning style10 modes available
Build: User List Screen
This screen shows a list of users fetched using a repository pattern. The repository handles data fetching and caching logic, separating data access from UI.
Target UI
-------------------------
| User List             |
|-----------------------|
| - Loading...          |
|                       |
|                       |
|                       |
|                       |
-------------------------
Create a UserRepository class that fetches user data from a fake API (simulate with Future.delayed).
UserRepository should have a method getUsers() returning a list of user names.
Create a UserListScreen widget that uses UserRepository to load and display users.
Show a loading indicator while fetching data.
Display the list of user names in a scrollable ListView.
Starter Code
Flutter
import 'package:flutter/material.dart';

// TODO: Create UserRepository class here

class UserListScreen extends StatefulWidget {
  @override
  State<UserListScreen> createState() => _UserListScreenState();
}

class _UserListScreenState extends State<UserListScreen> {
  // TODO: Declare variables for users and loading state

  @override
  void initState() {
    super.initState();
    // TODO: Load users from repository
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('User List')),
      body: Center(
        // TODO: Show loading or ListView of users
      ),
    );
  }
}
Task 1
Task 2
Task 3
Task 4
Task 5
Solution
Flutter
import 'package:flutter/material.dart';

class UserRepository {
  Future<List<String>> getUsers() async {
    await Future.delayed(Duration(seconds: 2));
    return ['Alice', 'Bob', 'Charlie', 'Diana'];
  }
}

class UserListScreen extends StatefulWidget {
  @override
  State<UserListScreen> createState() => _UserListScreenState();
}

class _UserListScreenState extends State<UserListScreen> {
  final UserRepository _userRepository = UserRepository();
  List<String> _users = [];
  bool _isLoading = true;

  @override
  void initState() {
    super.initState();
    _loadUsers();
  }

  void _loadUsers() async {
    final users = await _userRepository.getUsers();
    setState(() {
      _users = users;
      _isLoading = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('User List')),
      body: Center(
        child: _isLoading
            ? CircularProgressIndicator()
            : ListView.builder(
                itemCount: _users.length,
                itemBuilder: (context, index) {
                  return ListTile(
                    title: Text(_users[index]),
                  );
                },
              ),
      ),
    );
  }
}

We created a UserRepository class that simulates fetching user data with a 2-second delay. This class has a getUsers() method returning a list of user names.

In the UserListScreen widget, we use this repository to load users asynchronously in initState. While loading, a circular progress indicator shows. After loading, the user names display in a scrollable list.

This separation of data fetching logic into a repository helps keep UI code clean and makes it easier to change data sources later.

Final Result
Completed Screen
-------------------------
| User List             |
|-----------------------|
| • Alice               |
| • Bob                 |
| • Charlie             |
| • Diana               |
|                       |
|                       |
-------------------------
When the screen opens, a loading spinner shows for 2 seconds.
After loading, the list of user names appears.
User can scroll the list if it grows longer.
Stretch Goal
Add a Refresh button in the AppBar to reload the user list.
💡 Hint
Add an IconButton in AppBar actions that calls _loadUsers() when pressed.

Practice

(1/5)
1. What is the main purpose of the Repository pattern in Flutter app development?
easy
A. To manage app navigation
B. To create UI widgets faster
C. To handle user input events
D. To separate data access logic from UI code

Solution

  1. Step 1: Understand the role of Repository pattern

    The Repository pattern is designed to separate data access from the UI, so the UI does not directly handle data fetching or storage.
  2. Step 2: Identify the correct purpose

    Among the options, only separating data access logic from UI code matches the Repository pattern's purpose.
  3. Final Answer:

    To separate data access logic from UI code -> Option D
  4. Quick Check:

    Repository pattern = separate data access from UI [OK]
Hint: Repository = data access separated from UI [OK]
Common Mistakes:
  • Confusing Repository with UI widget creation
  • Thinking Repository manages navigation
  • Assuming Repository handles user input
2. Which of the following is the correct way to declare a repository interface in Dart for fetching a list of users?
easy
A. abstract class UserRepository { Future fetchUsers(); }
B. class UserRepository { List<User> fetchUsers() async {} }
C. interface UserRepository { List<User> fetchUsers(); }
D. abstract UserRepository { Future fetchUsers() List<User>; }

Solution

  1. Step 1: Recall Dart syntax for abstract classes

    In Dart, interfaces are created using abstract classes with method signatures but no bodies.
  2. Step 2: Check method signature for async fetching

    The method should return a Future to indicate asynchronous fetching.
  3. Final Answer:

    abstract class UserRepository { Future fetchUsers(); } -> Option A
  4. Quick Check:

    Dart interface = abstract class with Future method [OK]
Hint: Use abstract class with Future return for async methods [OK]
Common Mistakes:
  • Using 'interface' keyword which Dart doesn't have
  • Missing Future for async methods
  • Incorrect method syntax or return type
3. Given this repository implementation snippet, what will be printed?
class UserRepositoryImpl implements UserRepository {
  @override
  Future fetchUsers() async {
    return ['Alice', 'Bob'];
  }
}

void main() async {
  var repo = UserRepositoryImpl();
  var users = await repo.fetchUsers();
  print(users.length);
}
medium
A. 2
B. ['Alice', 'Bob']
C. Error: fetchUsers is not async
D. 0

Solution

  1. Step 1: Understand the fetchUsers method

    The method returns a Future with a list containing two strings: 'Alice' and 'Bob'.
  2. Step 2: Analyze the main function output

    The code awaits the fetchUsers call, then prints the length of the list, which is 2.
  3. Final Answer:

    2 -> Option A
  4. Quick Check:

    List length = 2 [OK]
Hint: Count list items returned by async method [OK]
Common Mistakes:
  • Confusing printed list with its length
  • Ignoring async/await causing errors
  • Assuming fetchUsers returns empty list
4. Identify the error in this repository implementation:
class UserRepositoryImpl implements UserRepository {
  Future fetchUsers() {
    return ['Alice', 'Bob'];
  }
}
medium
A. No error, code is correct
B. fetchUsers must return Future, but returns List<String>
C. UserRepositoryImpl should not implement UserRepository
D. Missing async keyword before fetchUsers method

Solution

  1. Step 1: Check return type of fetchUsers

    The method signature says it returns Future, but the returned value is a List<String> without wrapping in Future.
  2. Step 2: Identify type mismatch and missing Future

    Returning a plain list instead of a Future causes a type mismatch error.
  3. Final Answer:

    fetchUsers must return Future, but returns List<String> -> Option B
  4. Quick Check:

    Return type mismatch = fetchUsers must return Future, but returns List<String> [OK]
Hint: Check method return matches declared Future type [OK]
Common Mistakes:
  • Forgetting to wrap return value in Future
  • Ignoring type mismatch between User and String
  • Assuming async keyword is always required
5. You want to combine data from a local database and a remote API in your repository. Which approach best follows the Repository pattern principles?
hard
A. Use only remote API and ignore local DB to simplify code
B. Let UI call local DB and remote API separately and merge data itself
C. Create a repository that fetches from local DB first, then updates from remote API, exposing a single method to UI
D. Create two separate repositories and let UI decide which to use

Solution

  1. Step 1: Understand Repository pattern goal

    The pattern centralizes data access, hiding multiple sources behind one interface.
  2. Step 2: Evaluate options for combining data sources

    Create a repository that fetches from local DB first, then updates from remote API, exposing a single method to UI combines local and remote data inside one repository method, exposing a simple interface to UI, which matches the pattern.
  3. Step 3: Reject options that expose data sources to UI

    Options B and D let UI handle data sources, breaking separation. Use only remote API and ignore local DB to simplify code ignores local data, losing benefits.
  4. Final Answer:

    Create a repository that fetches from local DB first, then updates from remote API, exposing a single method to UI -> Option C
  5. Quick Check:

    Repository hides multiple sources behind one interface [OK]
Hint: Repository hides data sources, exposes one method to UI [OK]
Common Mistakes:
  • Letting UI handle multiple data sources
  • Ignoring local cache benefits
  • Splitting data sources into separate repositories without abstraction
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard