Jekyll Blog to Flutter News Application : Part 1 - Introduction.
Convert your Jekyll blog into a fully functional Flutter 2 mobile application.
Introduction
Like the title indicates, in this series of articles, we will reach into the steps for convert an existing Jekyll Blog Website to a fully functional Flutter Mobile app. I will do my best to be as clear as possible, but some passages may be blurry because I am at the same time improving my English.
So let's began.
The content of these articles will be divide into three main parts.
- Jekyll blogging website and GitHub pages.
- Communication between the application and the website via Jekyll JSON Posts API.
- User interface and features of the Flutter mobile app.
I will not dwell on some topics, such as setting up Github Pages or customizing a Jekyll blog, because that is not the purpose of this article.
Part 1.
1. Jekyll Blogging and GitHub Pages.
Jekyll + GitHub Pages is one of the best couples to quickly set up a blog without significant financial investment. I started blogging with Jekyll without knowing a single Ruby syntax. I cloned a repository and started customizations.
For those who use other blogging / CMS platforms, I will soon write other articles covering the same path, but starting from Wordpress, GetPublii... In the rest of the article we will assume that you already have a Jekyll blog hosted online or locally.
If you don't have a Jekyll blog, but you want to follow the tutorial till the end, just clone a free template from Bootstrap Starter. You will customize it later. I particularly like Mediumish.
At this step, i assume that you have a forked a Jekyll blog on GitHub. To host your pages with GitHub just go to your repo settings, section GitHub pages. You can activate hosting there and even setup custom domain name. By default GitHub associate your blog page to your github account ([username].github.io).
You are not limited to GitHub ; if you prefere GitLab, you can use instead GitLab Pages.
2. Communication between the application and the website via Jekyll JSON Posts API.
The best way to make a mobile app communicate with a web service is to create a RESTful API. As with the Jekyll blog, the content is generated once when changes are detected, we do not need to configure a web service. All we need is a JSON endpoint which at all times contains all the blog information such as all the articles and other useful data.
To achieve this, we need to have a little knowledge of how the Jekyll template works and the layout architecture. We also need to deepen our knowledge of Liquid filters.
For this tutorial we will use this model :
layout: null
permalink: /api/ #So your API will be accessible at <mydomain>/api
---
{
"homepage" : "{{ site.baseurl }}",
"version" : "1",
"last_update" : "{{ site.time | date_to_xmlschema }}",
"name": {{ site.name | smartify | jsonify}},
"description": "{{ site.description }}",
"logo": "{{ site.logo }}",
"expired": false,
"favicon": "{{ site.favicon }}",
"url": "{{ site.baseurl }}",
"disqus": "{{ site.disqus }}",
"email": "{{ site.email }}",
"paginate": "{{ site.paginate }}",
"authors": {{ site.authors | jsonify}},
"posts" : [{% for post in site.posts %}
{
"id":"{{post.id | slugify: "latin"}}",
"slug":"{{post.title | slugify}}",
"url":"{{site.baseurl}}{{post.url}}",
"media":"MEDIA_NAME",
"title":"{{post.title}}",
"featured":{%if post.featured == nil || post.featured == '' %}false{% else %}{{post.featured}}{% endif %},
"premium":{%if post.premium == nil || post.premium == '' %}false{% else %}{{post.premium}}{% endif %},
"date":"{{post.date}}",
"image":"{{ site.baseurl }}/{{post.image}}",
"author":{{site.authors[post.author] | jsonify}},
"read_time":"{{post.content | number_of_words | divided_by:180 }}",
"date_iso":"{{ post.date | date_to_xmlschema }}",
"date_en" : {
"day" : "{{ post.date | date: "%d" }}",
"month" : "{{ post.date | date: "%B" }}",
"year" : "{{ post.date | date: "%Y" }}",
"complete" : "{{ post.date | date: "%B, %d %Y" }}"
},
"date_fr" : {
"day" : "{{ post.date | date: "%d" }}",
"month" : {% assign m = post.date | date: "%-m" %}{% case m %}{% when '1' %}"Janvier"{% when '2' %}"Février"{% when '3' %}"Mars"{% when '4' %}"Avril"{% when '5' %}"Mai"{% when '6' %}"Juin"{% when '7' %}"Juillet"{% when '8' %}"Août"{% when '9' %}"Septembre"{% when '10' %}"Octobre"{% when '11' %}"Novembre"{% when '12' %}"Décembre"{% endcase %},
"year" : "{{ post.date | date: "%Y" }}",
"complete" : "{{ post.date | date: "%d" }} {% assign m = post.date | date: "%-m" %}{% case m %}{% when '1' %}Janvier{% when '2' %}Février{% when '3' %}Mars{% when '4' %}Avril{% when '5' %}Mai{% when '6' %}Juin{% when '7' %}Juillet{% when '8' %}Août{% when '9' %}Septembre{% when '10' %}Octobre{% when '11' %}Novembre{% when '12' %}Décembre{% endcase %} {{ post.date | date: "%Y" }}"
},
"category_main" : "{{post.categories | first }}",
"categories" : {{post.categories | jsonify }},
"tags" : {{post.tags | jsonify }},
{%if post.next == nil %}"next" : {}, {% else %}"next" : {
"id":"{{post.next.id | slugify: "latin"}}",
"slug":"{{post.next.title | slugify}}",
"url":"{{site.url}}{{post.next.url}}",
"title":"{{post.next.title}}",
"image":"{{site.baseurl}}/{{post.next.image}}",
"author":{{site.authors[post.next.author] | jsonify}},
"read_time":"{{post.next.content | number_of_words | divided_by:180 }}",
"date_iso":"{{ post.next.date | date_to_xmlschema }}",
"date_fr" : "{{ post.next.date | date: "%B, %d %Y" }}",
"date_en" : "{{ post.next.date | date: "%d" }} {% assign m = post.next.date | date: "%-m" %}{% case m %}{% when '1' %}Janvier{% when '2' %}Février{% when '3' %}Mars{% when '4' %}Avril{% when '5' %}Mai{% when '6' %}Juin{% when '7' %}Juillet{% when '8' %}Août{% when '9' %}Septembre{% when '10' %}Octobre{% when '11' %}Novembre{% when '12' %}Décembre{% endcase %} {{ post.next.date | date: "%Y" }}",
"category_main" : "{{post.next.categories | first }}",
"type":"{{post.next.layout}}",
"tags" : {{post.next.tags | jsonify }}
}, {% endif %}
{%if post.previous == nil %}"previous" : {}, {% else %}"previous" : {
"id":"{{post.previous.id | slugify: "latin"}}",
"slug":"{{post.previous.title | slugify}}",
"url":"{{site.url}}{{post.previous.url}}",
"title":"{{post.previous.title}}",
"image":"{{site.baseurl}}/{{post.previous.image}}",
"author":{{site.authors[post.previous.author] | jsonify}},
"read_time":"{{post.previous.content | number_of_words | divided_by:180 }}",
"date_iso":"{{ post.previous.date | date_to_xmlschema }}",
"date_fr" : "{{ post.previous.date | date: "%B, %d %Y" }}",
"date_en" : "{{ post.previous.date | date: "%d" }} {% assign m = post.previous.date | date: "%-m" %}{% case m %}{% when '1' %}Janvier{% when '2' %}Février{% when '3' %}Mars{% when '4' %}Avril{% when '5' %}Mai{% when '6' %}Juin{% when '7' %}Juillet{% when '8' %}Août{% when '9' %}Septembre{% when '10' %}Octobre{% when '11' %}Novembre{% when '12' %}Décembre{% endcase %} {{ post.previous.date | date: "%Y" }}",
"category_main" : "{{post.previous.categories | first }}",
"type":"{{post.previous.layout}}",
"tags" : {{post.previous.tags | jsonify }}
}, {% endif %}
"comments" : "",
"summary":{{post.excerpt | smartify | jsonify }},
"content":{{post.content | jsonify }}
} {% unless forloop.last %},{% endunless %}
{% endfor %}
]
}
So basically, this API returns blog information (logo, homepage, email, pagination, authors ...), saved in the Jekyll configuration file (_config.yml). Some information may seem unnecessary or redundant, such as the link to the homepage or the name of the blog. But they can be useful when you want to convert multiple blogs into one app.
The most important thing then is to get all the articles. This is done with this syntax {% for post in site.posts %}
, which goes through all the articles.
Apart from the basic information like the title, the URL, the date of the article, the image, the category, the tags, the summary and the content, we also collect some information which will be useful later:
- premium field will be used to Configure Google Ads service.
- The previous and next fields contain information on other articles related to this article.
- disqus field will helps us setup comments section with Disqus platform.
- Fields formatted like date_lang are useful for internalization in a simplier way. You can also do it, in Flutter with _dateiso field and throw package like intl.
⚠️ You can name this file as you see fit, but it must obligatorily end with the .json
extension to force Jekyll engine to render it as a JSON file and not as a HTML one. I named mine simply as posts_api.json. This is how it looks when the API is implemented.
3. User interface and features of the Flutter mobile app.
So based solely on the number of stars on GitHub, Flutter is one of the most popular mobile app development platforms in recent years.
Flutter 2 was announced recently and offers some very interesting features, so we will be using this version. To follow and understand the features that will be used in this part, you don't need to be a Flutter expert. One year ago, I had never written Dart code.
So, here are the features we will develop:
- Jekyll JSON API Modeling & Integration.
- Link the app to a specific domain name (this is for those who have setup a custom domain for their blog).
- Limit app data usage (with caching & image backup).
- Offline mode.
- Integration of Google Ads & Facebook Network Audience.
- State management with Riverpod.
- Night Mode.
- Comments section with Disqus.
- Internationalization.
- Push Notifications.
A single article cannot cover all of these features and implementations. This tutorial will therefore be divided into several parts with the complete implementation of at least one feature per part.
So see you in the next chapter of this series to learn how to :
- Create our flutter application.
- Setup models according to our Jekyll Blog Posts API.
- Connect our models with the hosted API.
- Dynamize our interfaces by connecting it with the data received from the API.
- Manage our application states with Riverpod.
As already said, this is my first technical write. So feel free to reach me if something is not clear and to give advice so that future articles will be even more useful and understandable.