Beyond Composability: Using Uniform as an Internal Tool
Tags: flutter, flutterdev, fullstackmobile, mobile
Published: 2023-04-18 07:37:01

According to [Internal](https://www.internal.io/), one of the leading Internal Tools providers, IT and engineering teams spend 40% of their time building and maintaining internally-facing applications and workflows utilized within their organization. It has led to organizations having dedicated software engineering teams or leveraging internal tooling platforms to cater for their organization's needs.

In this article, we will explore what Internal Tool is and how we can leverage Uniform as an Internal tool to build a News timeline application in Flutter.

## What are Internal Tools?

Internal Tools are any internal-facing software developed and utilized by a company to support internal operations. Based on the company’s needs, the decision about Internal Tooling usually boils down to building it internally (flexible and with no vendor lock-ins) or purchasing it as a service. The following are some of the advantages:

-   Cost-effective
-   Improved security
-   Time-saving
-   More control and increased productivity
-   Automate repetitive task

The most common internal tools help companies serve customer queries, analyze users’ behaviour, manage content, and manage APIs and requests.

Despite its offering, Internal Tools also comes with its baggage. Beyond the learning curve, the cost of subscription and maintenance, among others. It is especially difficult for small organizations with limited resources to either dedicate software engineering teams or subscribe to an Internal Tooling platform to help support internal operations.

## Exploring Uniform and its offering

Uniform is a digital experience platform that allows companies or individuals to frictionlessly adopt traditional and headless technologies without the associated overheads. It allows businesses to deliver built-in high-performance testing and personalization orchestration. Beyond it being a digital experience platform for composing experience, it offers some unique functionalities that small to large companies can leverage as an internal tool.

**Multiple integration support**

Uniform supports more than **40** integrations ranging from Content Delivery Networks, Headless Content Management Systems, Analytic tools, Email Management and Media Management. The platform makes integration easy by eliminating the need for manual connections.

**No-code solution**

The platform provides an intuitive user experience that developers, marketers, content developers, and other practitioners need to cater for business operations with little to no IT and ops involvement.

**Source of truth**

Uniform provides a truly composable digital experience with support for multiple stacks ranging from Commerce, CMS, Data, CDNs, etc. It delivers it as a single source of truth through its secure and fast API.

**Best-in-class User Experience**

Beyond the intuitive User Interface, Uniform also caters for developers by providing best-in-class developer documentation, SDKs, and libraries.

# Building the News timeline with Flutter and Uniform

Now that we understand what Internal tools are and how we can leverage Uniform functionalities as one, let’s now build a News timeline with Flutter and Uniform.

## Prerequisites

To fully grasp the concepts presented in this tutorial, the following are required:

-   Basic understanding of Dart and Flutter
-   Flutter [SDK](https://flutter.dev/docs/get-started/install) installed
-   [Cloudinary](https://cloudinary.com/?utm_source=fullstackwriter&utm_medium=fullstackwriter-blog) account (create a free account [here](https://cloudinary.com/users/register/free?utm_source=fullstackwriter&utm_medium=fullstackwriter-blog))
-   [Uniform](https://uniform.dev/?utm_source=fullstackwriter&utm_medium=fullstackwriter-blog) account (create a free account [here](https://uniform.app/?signup=true?utm_source=fullstackwriter&utm_medium=fullstackwriter-blog))
-   Either [iOS Simulator](https://developer.apple.com/xcode/?utm_source=fullstackwriter&utm_medium=fullstackwriter-blog), [Android Studio](https://developer.android.com/studio/?utm_source=fullstackwriter&utm_medium=fullstackwriter-blog), or [Chrome web browser](https://support.google.com/chrome/answer/95346?hl=en-GB&co=GENIE.Platform%3DDesktop) to run the application

## Getting started

To get started, we need to clone the project by navigating to the desired directory and running the command below:

```bash
git clone https://github.com/Mr-Malomz/news_mobile && cd news_mobile
```

## Running the project

First, we need to install the project dependencies by running the command below:

```bash
flutter pub get
```

Then, run the project using the following command:

```bash
flutter run
```

The command above will run the application on the selected device.

![Running app](https://paper-attachments.dropboxusercontent.com/s_8B8DE197826E615A56C9B0BA251BF609FEAFE6464C5164D511CDD16FBDA05741_1681417663568_Screenshot+2023-04-05+at+22.55.21.png)

**Image Sourcing and Upload to Cloudinary**

To start building our application, we must upload sample images for our News timeline application.

Sample data

| **News title**                                                             | **Image URL**                                    |
| -------------------------------------------------------------------------- | ------------------------------------------------ |
| You’re Using ChatGPT Wrong! Here’s How to Be Ahead of 99% of ChatGPT Users | https://bit.ly/3zBNZYG                           |
| Web3 crash course: The essentials                                          | https://bit.ly/3UiOKzm                           |
| How to write and design good API documentation                             | [https://bit.ly/3GqxjqZ](https://bit.ly/3GqxjqZ) |

In our Cloudinary dashboard, we uploaded the images by clicking on the **Media Library** tab, clicking on **Upload**, selecting the **Web Address** option, inputting the **URL**, and clicking on the **Arrow Button** to upload.

![select web address and enter url](https://paper-attachments.dropboxusercontent.com/s_8B8DE197826E615A56C9B0BA251BF609FEAFE6464C5164D511CDD16FBDA05741_1681418579584_Screenshot+2023-04-13+at+21.41.29.png)

After uploading the image, we will see it displayed on the console.

![displayed images](https://paper-attachments.dropboxusercontent.com/s_8B8DE197826E615A56C9B0BA251BF609FEAFE6464C5164D511CDD16FBDA05741_1681418617013_Screenshot+2023-04-13+at+21.43.16.png)

## Putting it all together on Uniform

With that done, we can start creating component libraries on Uniform. To do this, we must [sign up](https://uniform.app/?signup=true) and fill in the required information.

Next, input desired project name and click **Continue**.

![](https://paper-attachments.dropboxusercontent.com/s_37F75982472A48A1CBE0DA45CF281D6DB758F5E45F07A898000FDFD9A91BD422_1660256938530_Screenshot+2022-08-11+at+23.26.13.png)

Next, navigate to the **Security** tab, select **API Keys**, and click on the **Add API key** button to create one. Input `news_mobile` as the API name, select `Developer` as the Role and click the **Create API Key** button to create the API key.

![Create API key](https://paper-attachments.dropboxusercontent.com/s_8B8DE197826E615A56C9B0BA251BF609FEAFE6464C5164D511CDD16FBDA05741_1681502262134_Screenshot+2023-04-14+at+20.47.37.png)

![Input details](https://paper-attachments.dropboxusercontent.com/s_8B8DE197826E615A56C9B0BA251BF609FEAFE6464C5164D511CDD16FBDA05741_1681502277112_Screenshot+2023-04-14+at+20.55.23.png)

With this done, we should see a screen containing our **API Key** and **Project ID**. We need to copy these values as they will come in handy when building our application with Flutter.

![API Key and project ID](https://paper-attachments.dropboxusercontent.com/s_8D2B83BEFF067744B248404134093295EA9C4A67D2B7A1304F669F8CDBA7685A_1667940660042_Screenshot+2022-11-08+at+21.47.11.png)

**How modelling works in Uniform**

Uniform uses the concept of Components and Compositions to model application needs. **Components** in Uniform application work similarly to those in software development; it lets us break our application into smaller reusable building blocks with properties, while a **Composition** is the combination of one or more components.

![Application model](https://paper-attachments.dropboxusercontent.com/s_8B8DE197826E615A56C9B0BA251BF609FEAFE6464C5164D511CDD16FBDA05741_1681507589327_Desktop+-+1.png)

**Add Cloudinary integration support**

Uniform improves the product’s digital experience through integration with an existing system. To connect Cloudinary to our project, we need to navigate to the **Home** tab, click on the project, and navigate to the **Integrations** tab.

![Add Integration](https://paper-attachments.dropboxusercontent.com/s_8B8DE197826E615A56C9B0BA251BF609FEAFE6464C5164D511CDD16FBDA05741_1681503481946_Screenshot+2023-04-14+at+21.16.33.png)

Search or browse through the available integrations, select the **Cloudinary** integration, click on the **Add to project** button, input the `Cloudname`, `API Key`, **Test and Save**.

![Select BigCommerce](https://paper-attachments.dropboxusercontent.com/s_035C76640580F3EB13D6488A93030B16540CC5E21C7C5068391B80FD8F58126A_1660944439325_Screenshot+2022-08-19+at+22.24.21.png)

![Add Integration](https://paper-attachments.dropboxusercontent.com/s_035C76640580F3EB13D6488A93030B16540CC5E21C7C5068391B80FD8F58126A_1660944446044_Screenshot+2022-08-19+at+22.24.48.png)

![Add Cloudname & API Key](https://paper-attachments.dropboxusercontent.com/s_8B8DE197826E615A56C9B0BA251BF609FEAFE6464C5164D511CDD16FBDA05741_1681503762545_Screenshot+2023-04-14+at+21.21.53.png)

We can get our **Cloud Name** and **API Key** from our Cloudinary dashboard.

![Cloudinary Details](https://paper-attachments.dropboxusercontent.com/s_8B8DE197826E615A56C9B0BA251BF609FEAFE6464C5164D511CDD16FBDA05741_1681504137856_Screenshot+2023-04-14+at+21.24.42.png)

**Create components**

To get started, navigate to the **Home** tab and click on the project. Then click on the **Content** menu, select the **Components**, and click the **Add component** button.

![Create Component](https://paper-attachments.dropboxusercontent.com/s_8B8DE197826E615A56C9B0BA251BF609FEAFE6464C5164D511CDD16FBDA05741_1681507795225_Screenshot+2023-04-07+at+22.21.28.png)

| **Parameter Name** | **Help Text** | **Type**         | required |
| ------------------ | ------------- | ---------------- | -------- |
| news_title         | news title    | text             | YES      |
| news_image         | news image    | Cloudinary Image | YES      |

Input `news_timeline` as the component name, select **smartphone** as the icon, add properties of `news_title`, and `news_image` as shown above, and then click **OK**.

![component name](https://paper-attachments.dropboxusercontent.com/s_8B8DE197826E615A56C9B0BA251BF609FEAFE6464C5164D511CDD16FBDA05741_1681507985450_Screenshot+2023-04-06+at+22.00.31.png)
![component properties](https://paper-attachments.dropboxusercontent.com/s_8D2B83BEFF067744B248404134093295EA9C4A67D2B7A1304F669F8CDBA7685A_1667942446210_Screenshot+2022-10-25+at+21.33.01.png)
![added properties](https://paper-attachments.dropboxusercontent.com/s_8B8DE197826E615A56C9B0BA251BF609FEAFE6464C5164D511CDD16FBDA05741_1681508150807_Screenshot+2023-04-14+at+22.35.40.png)

Then click on the **Save and Close** button.

![save and close](https://paper-attachments.dropboxusercontent.com/s_8B8DE197826E615A56C9B0BA251BF609FEAFE6464C5164D511CDD16FBDA05741_1681508293091_Screenshot+2023-04-14+at+22.37.36.png)

Now that we have created the `news_timeline`, it will serve as a blueprint/building block for creating our News timeline application.

To start, navigate to the **Component** screen, click the **Add component** button, input `news_screen` as the component name, and check the **Composition Component.** Then navigate to the **Slots** section, and click the **Add slot** to create a slot.

![Add slot](https://paper-attachments.dropboxusercontent.com/s_8B8DE197826E615A56C9B0BA251BF609FEAFE6464C5164D511CDD16FBDA05741_1681508594794_Screenshot+2023-04-06+at+22.26.16.png)

**PS**: Slots help us create instances of our component and allow them to accept data dynamically.\*

Input `news_screen` as the Slot Name and click **OK**.

![create slots](https://paper-attachments.dropboxusercontent.com/s_8B8DE197826E615A56C9B0BA251BF609FEAFE6464C5164D511CDD16FBDA05741_1681508690287_Screenshot+2023-04-06+at+22.30.42.png)

Then click on the **Save and Close** button.

With that done, we can start using the `news_screen` component to compose our News timeline application. To get started, navigate to the **Composition** tab, select the `news_screen` as the composition type, input `news_mobile` as the name, and **Create**.

![create composition](https://paper-attachments.dropboxusercontent.com/s_8B8DE197826E615A56C9B0BA251BF609FEAFE6464C5164D511CDD16FBDA05741_1681508823124_Screenshot+2023-04-06+at+22.32.19.png)

Next, click on the **Plus Icon** to add a component to the composition.

![create component](https://paper-attachments.dropboxusercontent.com/s_8B8DE197826E615A56C9B0BA251BF609FEAFE6464C5164D511CDD16FBDA05741_1681508988051_Screenshot+2023-04-07+at+22.22.37.png)

Select the **news_timeline**, add the corresponding `name` and `image` for the component.

![select flower_component](https://paper-attachments.dropboxusercontent.com/s_8B8DE197826E615A56C9B0BA251BF609FEAFE6464C5164D511CDD16FBDA05741_1681509111266_Screenshot+2023-04-07+at+22.22.54.png)
![add data](https://paper-attachments.dropboxusercontent.com/s_8B8DE197826E615A56C9B0BA251BF609FEAFE6464C5164D511CDD16FBDA05741_1681509233687_Screenshot+2023-04-06+at+23.16.57.png)
![add data](https://paper-attachments.dropboxusercontent.com/s_8B8DE197826E615A56C9B0BA251BF609FEAFE6464C5164D511CDD16FBDA05741_1681509217898_Screenshot+2023-04-07+at+22.24.02.png)

We need to repeat the steps above to add the remaining **news_timeline** data. Then click on **Publish**. This makes our composition available to third-party applications.

![Hover on the item and click on the plus icon to add component](https://paper-attachments.dropboxusercontent.com/s_8B8DE197826E615A56C9B0BA251BF609FEAFE6464C5164D511CDD16FBDA05741_1681509397610_Screenshot+2023-04-07+at+22.24.29.png)
![Save and publish changes](https://paper-attachments.dropboxusercontent.com/s_8B8DE197826E615A56C9B0BA251BF609FEAFE6464C5164D511CDD16FBDA05741_1681509469907_Screenshot+2023-04-06+at+23.23.20.png)

We also need to note the **composition slug**; it will be useful when querying Uniform for our News data.

![composition slug](https://paper-attachments.dropboxusercontent.com/s_8B8DE197826E615A56C9B0BA251BF609FEAFE6464C5164D511CDD16FBDA05741_1681509620303_Screenshot+2023-04-07+at+22.26.41.png)

## Integrating Uniform with Flutter

With that done, we can start building the user interface and use Uniform to deliver the list of news seamlessly.

**Creating Application Model**

Uniform ships with a language-agnostic [Platform API](https://uniform.app/schemas/swagger.html) for managing and composing experience. We can test the API by filling in the **API Key**, **Project ID**, and **Slug**.

![API response from Uniform](https://paper-attachments.dropboxusercontent.com/s_8B8DE197826E615A56C9B0BA251BF609FEAFE6464C5164D511CDD16FBDA05741_1681509794422_Screenshot+2023-04-11+at+01.36.01.png)

With that in mind, we need to create a model to convert the response sent from Uniform to a Dart object. The model will also cater to JSON serialization. To do this, create an `utils.dart` file inside the `libs` folder and add the snippet below:

```dart
class RootComposition {
  Composition composition;
  String created;
  RootComposition({required this.composition, required this.created});
  factory RootComposition.fromJson(Map<String, dynamic> json) {
    return RootComposition(
      composition: Composition.fromJson(json['composition']),
      created: json['created'],
    );
  }
}

class Composition {
  Slots slots;
  Composition({required this.slots});
  factory Composition.fromJson(Map<String, dynamic> json) {
    return Composition(
      slots: Slots.fromJson(json['slots']),
    );
  }
}

class Slots {
  List<NewsScreen> news;
  Slots({required this.news});
  factory Slots.fromJson(Map<String, dynamic> json) {
    var data = json['newsScreen'] as List;
    return Slots(
      news: data.map((news) => NewsScreen.fromJson(news)).toList(),
    );
  }
}

class NewsScreen {
  Parameters parameters;
  NewsScreen({required this.parameters});
  factory NewsScreen.fromJson(Map<String, dynamic> json) {
    return NewsScreen(
      parameters: Parameters.fromJson(json['parameters']),
    );
  }
}

class Parameters {
  NewsImage newsImage;
  NewsTitle newsTitle;
  Parameters({required this.newsImage, required this.newsTitle});
  factory Parameters.fromJson(Map<String, dynamic> json) {
    return Parameters(
      newsImage: NewsImage.fromJson(json['newsImage']),
      newsTitle: NewsTitle.fromJson(json['newsTitle']),
    );
  }
}

class NewsImage {
  Value value;
  NewsImage({required this.value});
  factory NewsImage.fromJson(Map<String, dynamic> json) {
    return NewsImage(
      value: Value.fromJson(json\['value'\][0]),
    );
  }
}

class Value {
  String url;
  Value({required this.url});
  factory Value.fromJson(Map<String, dynamic> json) {
    return Value(
      url: json['url'],
    );
  }
}

class NewsTitle {
  String value;
  NewsTitle({required this.value});
  factory NewsTitle.fromJson(Map<String, dynamic> json) {
    return NewsTitle(
      value: json['value'],
    );
  }
}
```

Next, we must create a service file to separate the application core logic from the UI. To do this, create a `uniform_service.dart` file inside the `lib` directory. Then, add the snippet below:

```dart
import 'package:dio/dio.dart';
import 'package:news_mobile/utils.dart';

class UniformService {
  final dio = Dio();
  static const _apiKey = "REPLACE WITH API KEY";
  static const _projectID = "REPLACE WITH PROJECT ID";
  static const _slug = "newsMobile";

  var headers = {
    "content-type": "application/json",
    "x-api-key": _apiKey,
  };

  Future<RootComposition> getNews() async {
    var response = await dio.get(
      "https://uniform.app/api/v1/canvas?limit=100&projectId=$_projectID&slug=$_slug",
      options: Options(headers: headers),
    );

    if (response.statusCode == 200) {
      var resp = response.data;
      var news = RootComposition.fromJson(resp);
      return news;
    } else {
      throw Exception('Error getting news');
    }
  }
}
```

The snippet above does the following:

-   Imports the required dependencies
-   Creates a `UniformService` class with `_apiKey`, `_projectID`, `_slug` and `headers` properties.
-   Creates the `getNews` method that uses the `Dio` package to configure permissions and make secure HTTPS request to the Uniform API and returns the appropriate responses

## Consuming the service

With that done, we can use the service to perform the required operation. To do this, we need to modify the `home.dart` file in the `screens` directory as shown below:

```dart
import 'package:flutter/material.dart';
import 'package:news_mobile/uniform_service.dart';
import 'package:news_mobile/utils.dart';

class Home extends StatefulWidget {
  const Home({super.key});

  @override
  State<Home> createState() => _HomeState();
}

class _HomeState extends State<Home> {
  late RootComposition news;
  bool _isLoading = false;
  bool _isError = false;

  @override
  void initState() {
    _getNews();
    super.initState();
  }
  _getNews() {
    setState(() {
      _isLoading = true;
    });

    UniformService().getNews().then((value) {
      setState(() {
        news = value;
        _isLoading = false;
      });
    }).catchError((onError) {
      setState(() {
        _isLoading = false;
        _isError = true;
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return _isLoading
        ? const Center(
            child: CircularProgressIndicator(
            color: Colors.blue,
          ))
        : _isError
            ? const Center(
                child: Text(
                  'Error getting news',
                  style: TextStyle(
                    color: Colors.red,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              )
            : Scaffold(
                appBar: AppBar(
                  title: const Text('News Timeline'),
                  backgroundColor: Colors.black,
                ),
                body: ListView.builder(
                  itemCount: news.composition.slots.news.length,
                  itemBuilder: (context, index) {
                    return Container(
                      decoration: const BoxDecoration(
                        border: Border(
                          bottom: BorderSide(width: .5, color: Colors.grey),
                        ),
                      ),
                      padding: const EdgeInsets.fromLTRB(10, 20, 10, 20),
                      child: Row(
                        children: [
                          ClipRRect(
                            borderRadius: BorderRadius.circular(5.0),
                            child: Image.network(
                              news.composition.slots.news[index].parameters
                                  .newsImage.value.url,
                              height: 80.0,
                              width: 80.0,
                            ),
                          ),
                          const SizedBox(width: 15.0),
                          Expanded(
                            child: Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                Text(
                                  news.composition.slots.news[index].parameters
                                      .newsTitle.value,
                                  style: TextStyle(
                                      color: Colors.black,
                                      fontWeight: FontWeight.w800),
                                ),
                                SizedBox(height: 10.0),
                                Text(
                                  news.created.substring(0, 10),
                                  style: TextStyle(
                                    color: Colors.grey,
                                  ),
                                )
                              ],
                            ),
                          ),
                        ],
                      ),
                    );
                  },
                ),
              );
  }
}
```

The snippet above does the following:

-   Imports the required dependencies
-   **Lines 13-15**: Create the `news`, `_isLoading`, and `_isError` properties to manage the application state
-   **Lines 17-39**: Create a `_getNews` method to get the list of news from Uniform using the `UniformService().getNews` and set states accordingly
-   Modifies the UI to use the states and method created to get the news list

With that done, we restart the application using the code editor or run the command below:

```bash
flutter run
```

![Running app](https://paper-attachments.dropboxusercontent.com/s_8B8DE197826E615A56C9B0BA251BF609FEAFE6464C5164D511CDD16FBDA05741_1681513076315_Screenshot+2023-04-11+at+01.34.20.png)

## Conclusion

This post discussed what Internal Tools are, Uniform’s uniqueness, and how to build a News timeline with Flutter. Beyond what was discussed above, Uniform also caters for marketers' and content strategies' operational needs with little or no IT and engineering team involvement.

These resources might be helpful:

-   [Uniform Platform API](https://uniform.app/schemas/swagger.html?utm_source=fullstackwriter&utm_medium=fullstackwriter-blog)
-   [Uniform official documentation](https://docs.uniform.app/?utm_source=fullstackwriter&utm_medium=fullstackwriter-blog)
-   [Cloudinary official documentation](https://cloudinary.com/documentation/?utm_source=fullstackwriter&utm_medium=fullstackwriter-blog)