So this week started with me working on the buggy Interactive Book part of the app. We hit this error:
══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞══
'package:flutter_markdown/src/builder.dart': Failed assertion: line 267 pos 12: '_inlines.isEmpty': is not true.
The widget causing it was:
SingleChildScrollView:file:///C:/Users/emper/Desktop/cv_app_gsoc/lib/ui/views/ib/ib_page_view.dart:501:22
This is an assertion failure in the flutter_markdown
package, and basically, the parser found some weird inline elements in our Markdown that it couldn’t handle. Either some broken tags or bad formatting. Now we were already using a sanitizeMarkdown()
function (I’d mentioned that in the last blog), to catch broken lines and tags but still, this popped up.
Digging deeper, I noticed this in our custom builders:
RegExp get pattern => RegExp(r'{:\s?(.+)\s?}');
This is for extracting content between {:
and }
. We had more such patterns, and I was trying all ways to fix things from the UI side wrapping in an error boundary, adding debug logs, etc.
Also ran into a null operator used on a null value
kind of thing, so I added proper checks for that as well.
But then… Hardik pinged
Hardik asked me to pause this bug-hunt for a while and work on releasing the app — a basic GitHub release
so others in the community can try it too.
This was literally the first time I was doing anything with CI/CD — had zero idea about it 😅. So I just told my mentors: “Yeh we can do that, but I don’t have much knowledge on this… could you share something I can start with?”
Hardik shared this article:
👉 Automating Flutter Builds with GitHub Actions: A Step-by-Step Guide
It was actually really well written and easy to understand. But in that article, they used a single file for the workflow.
In our project, we had three files: main.yml
, ci.yml
, and cd.yml
.
So I read more and turns out it’s perfectly fine. You use one file if your project is small, but for a bit more structure, people prefer separate files. In our case:
main.yml
: Just validates commit messages (we're usingaction-conventional-commits
). Triggers onpull_request
.ci.yml
: Handles build, test, and code checks (formatting, analysis, coverage). Runs on every push/PR.cd.yml
: Manages release and deployment usingsemantic-release
,Fastlane
, GitHub Releases, etc. Triggers manually or onpush
tomaster
.
What I added
I added this part to build and upload the APK:
- name: Build APK (Android)
if: ${{ matrix.platform == 'ubuntu-latest' }}
run: |
flutter build apk --release \
--dart-define=FB_APP_ID=${{ secrets.FB_APP_ID }} \
--dart-define=FB_APP_NAME=${{ secrets.FB_APP_NAME }} \
--dart-define=GITHUB_OAUTH_CLIENT_ID=${{ secrets.GH_OAUTH_CLIENT_ID }} \
--dart-define=GITHUB_OAUTH_CLIENT_SECRET=${{ secrets.GH_OAUTH_CLIENT_SECRET }}
- name: Upload APK Artifact
if: ${{ matrix.platform == 'ubuntu-latest' }}
uses: actions/upload-artifact@v4
with:
name: android-apk-${{ github.run_number }}
path: build/app/outputs/apk/release/app-release.apk
if-no-files-found: error
Then I added the release section to automate GitHub release creation when code is pushed to master
:
release:
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
needs: build
runs-on: ubuntu-latest
steps:
- name: Download APK Artifact
uses: actions/download-artifact@v4
with:
name: android-apk-${{ github.run_number }}
path: ./
- name: Get Current Date
id: date
run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
- name: Create Release
uses: softprops/action-gh-release@v2
with:
tag_name: "v1.0.${{ github.run_number }}"
name: "Release v1.0.${{ github.run_number }} - ${{ steps.date.outputs.date }}"
body: |
Android APK release built from commit ${{ github.sha }}.
Build number: ${{ github.run_number }}
Build date: ${{ steps.date.outputs.date }}
Download and install the APK file below.
files: ./app-release.apk
draft: false
prerelease: false
generate_release_notes: true
To try what I learnt, I created a separate repo and tried out the learning here:
👉 Learning GitHub Workflow
The PR : Enhance CI with automated releases #398
After this, Hardik made the release public in the community and shared the APK. And guess what — people actually downloaded and ran it!
And then... F-Droid?
Someone in the community — salmoneatenbybear
aka Aditya (I hope I’m right 😅) — mentioned F-Droid deployment. I’ll be honest, I didn’t know what F-Droid was at that moment.
So I looked it up.
Turns out:
F-Droid is a free and open-source app store for Android.
It doesn’t track users, hosts only FOSS apps, and is maintained by the community.
It’s kinda like Play Store, but for open-source-only apps. Very cool.
I tried checking the last PR related to it (or maybe it wasn’t a PR), but it didn’t really come to a conclusion. Still need to explore it more.
If you're curious about how to contribute or deploy on F-Droid, here’s a good starting point: 📘 F-Droid Contribution Guide
Wrapping up...
So yeah — started the week trying to make Markdown behave, ended the week building CI/CD pipelines, publishing GitHub releases, and learning about app stores I never knew existed 😄
Next up: probably explore more around F-Droid, improve release notes, and see if I can finally fix that Markdown crash for good.
Thanks for reading! If you have suggestions, feel free to drop them in comments 🙌
Top comments (0)