Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/juuaaann456/DMI-Practica06/llms.txt

Use this file to discover all available pages before exploring further.

The presentation layer is responsible for everything the user sees and interacts with. It reads from the domain through Riverpod providers and never touches the infrastructure directly.

Structure

lib/presentation/
├── screens/
│   └── movies/
│       └── home_screen.dart
├── providers/
│   └── movies/
│       ├── movies_providers.dart
│       └── movies_repository_provider.dart
└── widgets/

Provider setup

Repository provider

The entry point for dependency injection is movieRepositoryProvider. It wires together the concrete datasource and repository, producing the single MoviesRepository instance used throughout the app.
final movieRepositoryProvider = Provider((ref) {
  return MovieRepositoryImpl(MoviedbDataSource());
});
This is the only place in the codebase where infrastructure classes are instantiated. Everything above this layer depends on the MoviesRepository abstraction.

MoviesNotifier and pagination

MoviesNotifier is a generic Notifier<List<Movie>> that manages an append-only list of movies. The same class powers every movie category by accepting a different callback at construction time.
typedef MovieCallback = Future<List<Movie>> Function({int page});

class MoviesNotifier extends Notifier<List<Movie>> {
  final MovieCallback Function(Ref ref) _callbackBuilder;
  late final MovieCallback fetchMoreMovies;

  MoviesNotifier(this._callbackBuilder);

  int currentPage = 0;
  bool isLoading = false;

  @override
  List<Movie> build() {
    fetchMoreMovies = _callbackBuilder(ref);
    return [];
  }

  Future<void> loadNextPage() async {
    if (isLoading) return;
    isLoading = true;

    currentPage++;
    final movies = await fetchMoreMovies(page: currentPage);

    state = [...state, ...movies];

    isLoading = false;
  }
}
Pagination mechanics:
  • currentPage starts at 0 and increments before each request, so the first call always fetches page 1
  • isLoading acts as a guard — calling loadNextPage() while a request is in flight is a no-op
  • New results are spread-appended to the existing state list, triggering a widget rebuild

Category providers

Each movie category gets its own NotifierProvider. They all share MoviesNotifier but are configured with a different repository method:
final nowPlayingMoviesProvider = NotifierProvider<MoviesNotifier, List<Movie>>(
  () => MoviesNotifier(
    (ref) => ref.watch(movieRepositoryProvider).getNowPlaying,
  ),
);

final popularMoviesProvider = NotifierProvider<MoviesNotifier, List<Movie>>(
  () => MoviesNotifier(
    (ref) => ref.watch(movieRepositoryProvider).getPopular,
  ),
);

final upcomingMoviesProvider = NotifierProvider<MoviesNotifier, List<Movie>>(
  () => MoviesNotifier(
    (ref) => ref.watch(movieRepositoryProvider).getUpcoming,
  ),
);

final topratedMoviesProvider = NotifierProvider<MoviesNotifier, List<Movie>>(
  () => MoviesNotifier(
    (ref) => ref.watch(movieRepositoryProvider).getTopRated,
  ),
);

final mexicanMoviesProvider = NotifierProvider<MoviesNotifier, List<Movie>>(
  () => MoviesNotifier(
    (ref) => ref.watch(movieRepositoryProvider).getMexicanMovies,
  ),
);
The _callbackBuilder pattern lets MoviesNotifier remain reusable across all five categories without subclassing. The callback is resolved lazily inside build() so that ref is available at the right lifecycle moment.

Home screen

HomeScreen is the main entry point for the app. It delegates all rendering to its internal _HomeView widget.
class HomeScreen extends StatelessWidget {
  static const name = 'home-screen';

  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: _HomeView(),
      bottomNavigationBar: CustomBottomNavigationbar(),
    );
  }
}
_HomeView is a ConsumerStatefulWidget so it can call providers in initState to trigger the initial page load for all five categories:
class _HomeViewState extends ConsumerState<_HomeView> {
  @override
  void initState() {
    super.initState();

    ref.read(nowPlayingMoviesProvider.notifier).loadNextPage();
    ref.read(popularMoviesProvider.notifier).loadNextPage();
    ref.read(topratedMoviesProvider.notifier).loadNextPage();
    ref.read(upcomingMoviesProvider.notifier).loadNextPage();
    ref.read(mexicanMoviesProvider.notifier).loadNextPage();
  }

  @override
  Widget build(BuildContext context) {
    final initialLoading = ref.watch(intialLoadingProvider);

    if (initialLoading) return const FullscreenLoader();

    final nowPlayingMovies = ref.watch(nowPlayingMoviesProvider);
    final popular = ref.watch(popularMoviesProvider);
    final topRated = ref.watch(topratedMoviesProvider);
    final upcomingMovies = ref.watch(upcomingMoviesProvider);
    final mexicanMovies = ref.watch(mexicanMoviesProvider);
    final slideShowMovies = ref.watch(movieSlideShowProvider);

    return CustomScrollView(
      slivers: [
        const SliverAppBar(floating: true, title: CustomAppbar()),
        SliverList(
          delegate: SliverChildBuilderDelegate(
            (context, index) {
              if (index > 0) return null;
              return Column(
                children: [
                  MovieSlideshow(movies: slideShowMovies),
                  MovieHorizontalListview(
                    movies: nowPlayingMovies,
                    title: 'En cines',
                    loadNextPage: () =>
                        ref.read(nowPlayingMoviesProvider.notifier).loadNextPage(),
                  ),
                  MovieHorizontalListview(
                    movies: upcomingMovies,
                    title: 'Próximamente',
                    loadNextPage: () =>
                        ref.read(upcomingMoviesProvider.notifier).loadNextPage(),
                  ),
                  // ... popular, topRated, mexicanMovies follow the same pattern
                ],
              );
            },
            childCount: 1,
          ),
        ),
      ],
    );
  }
}

Infinite scroll pattern

Each MovieHorizontalListview receives a loadNextPage callback. When the user scrolls to the end of a horizontal list, the widget calls the callback, which invokes loadNextPage() on the corresponding notifier. The notifier increments currentPage, fetches the next batch, and appends it to state — causing the list to grow without replacing existing items.
The isLoading guard inside MoviesNotifier.loadNextPage() means that rapid scroll events or multiple widget rebuilds cannot trigger duplicate network requests for the same page.

Build docs developers (and LLMs) love