From a WordPress Site to a Blazing-Fast Flutter App : REST API vs GraphQL

We started with a goal that many e-commerce store owners share: to take our successful WordPress and WooCommerce website and build a beautiful, native mobile app for our customers. We chose Flutter, a powerful framework for building apps on both iOS and Android from a single codebase.

Our initial version was a success—it worked! But we quickly ran into a common challenge: how do we make the app feel fast and smooth, and, most importantly, how do we stop it from breaking every time we update a plugin on our WordPress site?

This is the story of our journey—analyzing our app's architecture and discovering the modern, future-proof approach to make it faster, more reliable, and a joy to maintain.


Our Starting Point: A Look at Our Project's DNA

Before we dive into the changes, let's look at the foundation we built. Our app, caliber_app, has a very professional and well-organized structure. Understanding this is key to seeing why we're making these upgrades.

  • lib/blocs/: This is the "brain" of our app. We correctly separated every feature into its own Business Logic Component (BLoC). We have a

    ProductBloc, CartBloc, AuthenticationBloc, and many more. This keeps our logic clean and organized.

  • lib/domain/: This is the heart of our application, containing all the core business rules.

    • repositories/: These are our "data managers." They decide whether to fetch data from the internet (remote) or a local database (local).

    • models/: These are the "blueprints" for our data, defining what a Product, Store, or User looks like. We use

      freezed here, which is a fantastic tool for preventing bugs.

    • data/: This contains our "storage rooms"—the local_data_source.dart for connecting to our on-device Hive database and the remote_data_source (which is implicitly our CaliberApi).

  • lib/network/: This is our app's "communications department." Using

    Dio and Retrofit in caliber_api.dart, it handles all the technical details of talking to our WordPress website.

  • lib/screens/: This is everything the user sees—all our UI code, neatly organized by feature (home, cart, profile, etc.).

This structure is solid. But as we'll see, it led to some challenges that a new approach can solve.


The Challenge: The Traditional REST API Approach

Our app was built on the standard WordPress REST API. To load our home screen (home_body.dart), our app had to make many separate network requests:

  1. Ask for banners (using BannerBlocBloc).

  2. Ask for promos (using PromoBloc).

  3. Ask for new arrivals and featured products (using ProductBloc).

  4. Ask for stores (using StoreBloc).

  5. Ask for blogs (using BlogsBloc).

This is what we call a "chatty" API. It works, but it has two major drawbacks:

  • It’s Slow: Each of those five calls is a separate round trip to our server. The user feels this delay as a slow loading screen.

  • It’s Brittle: This was our biggest worry. If we changed the Dokan plugin, our StoreBloc might break. If we changed our custom banner system, our BannerBlocBloc would break. The app was too tightly coupled to the WordPress backend, making it fragile and high-maintenance.


The Solution: Embracing a Headless Approach with GraphQL ๐Ÿš€

We decided to upgrade to a modern architecture: a Headless CMS approach using GraphQL.

Instead of a dozen separate "service windows," GraphQL gives us a single, powerful endpoint. Our Flutter app can now send one detailed request (a "query") and get back all the data it needs for an entire screen in one go.

This solves our two biggest problems:

  • Speed: One network request is dramatically faster than five.

  • Durability: Our Flutter app is no longer tied to specific plugin endpoints. It just asks for the data it needs, and the GraphQL server figures out how to get it. If a plugin changes, we only have to update the logic in one place on our server, not in our app.


Our 3-Step Action Plan to Go Headless

Here's how we are refactoring our app, starting with the home screen.

Step 1: Prep Our WordPress Backend

We need to install and activate these free plugins on our WordPress site:

  1. WPGraphQL: The core engine that creates the /graphql endpoint.

  2. WooGraphQL: An essential add-on that exposes all our WooCommerce products, variations, and cart data.

  3. A GraphQL extension for Dokan: To get our store data, we'll need to find or build a GraphQL extension for the Dokan plugin.

With these active, we can use the built-in GraphiQL IDE in our WordPress dashboard to build and test our queries.

Step 2: Equip Our Flutter App

Next, we update our Flutter project to speak GraphQL.

  1. Add the Package: In pubspec.yaml, we add graphql_flutter.

  2. Configure the Client: In our dependency_injection.dart file, we'll set up the GraphQLClient.

    Dart
    // in dependency_injection.dart
    final HttpLink httpLink = HttpLink('https://calibershoes.com/graphql');
    
    final client = GraphQLClient(
      link: httpLink,
      cache: GraphQLCache(store: HiveStore()),
    );
    
    inject.registerLazySingleton<GraphQLClient>(() => client);
    

Step 3: Rewrite Our Home Screen Logic

This is where the magic happens.

First, we define one query to get all the data for our home screen (home_body.dart):

GraphQL
// This query replaces 5+ separate API calls!
query GetHomeScreenData {
  newArrivals: products(first: 5, where: {orderby: {field: DATE, order: DESC}}) {
    nodes { id, name, price, images { nodes { sourceUrl } } }
  }
  bestOffers: products(first: 5, where: {featured: true}) {
    nodes { id, name, price, images { nodes { sourceUrl } } }
  }
  stores(first: 5) {
    nodes { id, storeName, bannerImage }
  }
  blogs: posts(first: 4) {
    nodes { id, title, date, featuredImage { node { sourceUrl } } }
  }
}

Next, we replace the 5+ BLoCs for the home screen with a single HomeBloc. This BLoC will run the query above and emit a single state containing all the data.

Finally, we simplify our home_body.dart widget. Instead of multiple BlocConsumers, we'll have just one.

BEFORE:

Dart
// home_body.dart
// ... a BlocConsumer for Products ...
NewArrival(), 
// ... a BlocConsumer for Stores ...
CaliberOutlets(),
// ... a BlocConsumer for Blogs ...
BlogsScreen(),

AFTER:

Dart
// home_body.dart
BlocBuilder<HomeBloc, HomeState>(
  builder: (context, state) {
    if (state.isLoading) return LoadingShimmers();
    if (state.hasData) {
      return Column(
        children: [
          NewArrivalWidget(products: state.newArrivals),
          CaliberOutletsWidget(stores: state.stores),
          BlogsWidget(blogs: state.blogs),
        ],
      );
    }
  }
)

The individual widgets like NewArrivalWidget become much simpler. They no longer need their own BLoCs; they just receive the data they need to display.

Our Path Forward

This journey to a headless architecture is a significant upgrade. By investing the time to learn and refactor, we are building an app that is not only faster for our users but also dramatically more stable and easier for us to maintain. We're turning a brittle bridge into a modern superhighway, ensuring our app can easily handle whatever updates and changes come its way.

Post a Comment

Previous Post Next Post