Beyond Composability: Using Uniform as an Internal Tool
Explore what Internal Tool is and how to leverage Uniform as an Internal tool to build a News timeline application in Flutter.
Demola Malomo
Apr 17 2023
8 min read
According to Internal, 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 installed
- Cloudinary account (create a free account here)
- Uniform account (create a free account here)
- Either iOS Simulator, Android Studio, or Chrome web browser 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:
1git 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:
1flutter pub get
Then, run the project using the following command:
1flutter run
The command above will run the application on the selected device.
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 |
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.
After uploading the image, we will see it displayed on the console.
Putting it all together on Uniform
With that done, we can start creating component libraries on Uniform. To do this, we must sign up and fill in the required information.
Next, input desired project name and click Continue.
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.
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.
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.
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.
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.
We can get our Cloud Name and API Key from our Cloudinary dashboard.
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.
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.
Then click on the Save and Close button.
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.
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.
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.
Next, click on the Plus Icon to add a component to the composition.
Select the news_timeline, add the corresponding name
and image
for the component.
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.
We also need to note the composition slug; it will be useful when querying Uniform for our News data.
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 for managing and composing experience. We can test the API by filling in the API Key, Project ID, and Slug.
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:
1class RootComposition { 2 Composition composition; 3 String created; 4 RootComposition({required this.composition, required this.created}); 5 factory RootComposition.fromJson(Map<String, dynamic> json) { 6 return RootComposition( 7 composition: Composition.fromJson(json['composition']), 8 created: json['created'], 9 ); 10 } 11} 12 13class Composition { 14 Slots slots; 15 Composition({required this.slots}); 16 factory Composition.fromJson(Map<String, dynamic> json) { 17 return Composition( 18 slots: Slots.fromJson(json['slots']), 19 ); 20 } 21} 22 23class Slots { 24 List<NewsScreen> news; 25 Slots({required this.news}); 26 factory Slots.fromJson(Map<String, dynamic> json) { 27 var data = json['newsScreen'] as List; 28 return Slots( 29 news: data.map((news) => NewsScreen.fromJson(news)).toList(), 30 ); 31 } 32} 33 34class NewsScreen { 35 Parameters parameters; 36 NewsScreen({required this.parameters}); 37 factory NewsScreen.fromJson(Map<String, dynamic> json) { 38 return NewsScreen( 39 parameters: Parameters.fromJson(json['parameters']), 40 ); 41 } 42} 43 44class Parameters { 45 NewsImage newsImage; 46 NewsTitle newsTitle; 47 Parameters({required this.newsImage, required this.newsTitle}); 48 factory Parameters.fromJson(Map<String, dynamic> json) { 49 return Parameters( 50 newsImage: NewsImage.fromJson(json['newsImage']), 51 newsTitle: NewsTitle.fromJson(json['newsTitle']), 52 ); 53 } 54} 55 56class NewsImage { 57 Value value; 58 NewsImage({required this.value}); 59 factory NewsImage.fromJson(Map<String, dynamic> json) { 60 return NewsImage( 61 value: Value.fromJson(json\['value'\][0]), 62 ); 63 } 64} 65 66class Value { 67 String url; 68 Value({required this.url}); 69 factory Value.fromJson(Map<String, dynamic> json) { 70 return Value( 71 url: json['url'], 72 ); 73 } 74} 75 76class NewsTitle { 77 String value; 78 NewsTitle({required this.value}); 79 factory NewsTitle.fromJson(Map<String, dynamic> json) { 80 return NewsTitle( 81 value: json['value'], 82 ); 83 } 84}
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:
1import 'package:dio/dio.dart'; 2import 'package:news_mobile/utils.dart'; 3 4class UniformService { 5 final dio = Dio(); 6 static const _apiKey = "REPLACE WITH API KEY"; 7 static const _projectID = "REPLACE WITH PROJECT ID"; 8 static const _slug = "newsMobile"; 9 10 var headers = { 11 "content-type": "application/json", 12 "x-api-key": _apiKey, 13 }; 14 15 Future<RootComposition> getNews() async { 16 var response = await dio.get( 17 "https://uniform.app/api/v1/canvas?limit=100&projectId=$_projectID&slug=$_slug", 18 options: Options(headers: headers), 19 ); 20 21 if (response.statusCode == 200) { 22 var resp = response.data; 23 var news = RootComposition.fromJson(resp); 24 return news; 25 } else { 26 throw Exception('Error getting news'); 27 } 28 } 29}
The snippet above does the following:
- Imports the required dependencies
- Creates a
UniformService
class with_apiKey
,_projectID
,_slug
andheaders
properties. - Creates the
getNews
method that uses theDio
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:
1import 'package:flutter/material.dart'; 2import 'package:news_mobile/uniform_service.dart'; 3import 'package:news_mobile/utils.dart'; 4 5class Home extends StatefulWidget { 6 const Home({super.key}); 7 8 9 State<Home> createState() => _HomeState(); 10} 11 12class _HomeState extends State<Home> { 13 late RootComposition news; 14 bool _isLoading = false; 15 bool _isError = false; 16 17 18 void initState() { 19 _getNews(); 20 super.initState(); 21 } 22 _getNews() { 23 setState(() { 24 _isLoading = true; 25 }); 26 27 UniformService().getNews().then((value) { 28 setState(() { 29 news = value; 30 _isLoading = false; 31 }); 32 }).catchError((onError) { 33 setState(() { 34 _isLoading = false; 35 _isError = true; 36 }); 37 }); 38 } 39 40 41 Widget build(BuildContext context) { 42 return _isLoading 43 ? const Center( 44 child: CircularProgressIndicator( 45 color: Colors.blue, 46 )) 47 : _isError 48 ? const Center( 49 child: Text( 50 'Error getting news', 51 style: TextStyle( 52 color: Colors.red, 53 fontWeight: FontWeight.bold, 54 ), 55 ), 56 ) 57 : Scaffold( 58 appBar: AppBar( 59 title: const Text('News Timeline'), 60 backgroundColor: Colors.black, 61 ), 62 body: ListView.builder( 63 itemCount: news.composition.slots.news.length, 64 itemBuilder: (context, index) { 65 return Container( 66 decoration: const BoxDecoration( 67 border: Border( 68 bottom: BorderSide(width: .5, color: Colors.grey), 69 ), 70 ), 71 padding: const EdgeInsets.fromLTRB(10, 20, 10, 20), 72 child: Row( 73 children: [ 74 ClipRRect( 75 borderRadius: BorderRadius.circular(5.0), 76 child: Image.network( 77 news.composition.slots.news[index].parameters 78 .newsImage.value.url, 79 height: 80.0, 80 width: 80.0, 81 ), 82 ), 83 const SizedBox(width: 15.0), 84 Expanded( 85 child: Column( 86 crossAxisAlignment: CrossAxisAlignment.start, 87 children: [ 88 Text( 89 news.composition.slots.news[index].parameters 90 .newsTitle.value, 91 style: TextStyle( 92 color: Colors.black, 93 fontWeight: FontWeight.w800), 94 ), 95 SizedBox(height: 10.0), 96 Text( 97 news.created.substring(0, 10), 98 style: TextStyle( 99 color: Colors.grey, 100 ), 101 ) 102 ], 103 ), 104 ), 105 ], 106 ), 107 ); 108 }, 109 ), 110 ); 111 } 112}
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 theUniformService().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:
1flutter run
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: