<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Kosrat</title><description>Mobile Developer</description><link>https://kosrat.dev/</link><language>en</language><item><title>Flutter Ship 01: Git &amp; GitHub Workflow</title><link>https://kosrat.dev/posts/flutter-ship/git-github/</link><guid isPermaLink="true">https://kosrat.dev/posts/flutter-ship/git-github/</guid><description>Learn to set up a production-ready Git &amp; GitHub workflow for Flutter projects. Covers branching strategy, conventional commits, automated changelogs, git hooks, and CI/CD with GitHub Actions.</description><pubDate>Sun, 20 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::note[Updated]
This post has been updated at &lt;strong&gt;2 September 2025&lt;/strong&gt; with improved Git hook and PR Checks configurations.
:::&lt;/p&gt;
&lt;h1&gt;Overview&lt;/h1&gt;
&lt;p&gt;More than two decades ago, &lt;strong&gt;Git&lt;/strong&gt; emerged as a version control system (&lt;strong&gt;VCS&lt;/strong&gt;) to facilitate teamwork and track changes in a version history. Nowadays, Git is an essential tool for software developers, especially for those working in a team. Complementing Git, platforms like &lt;strong&gt;GitHub&lt;/strong&gt; provide hosting for Git repositories, enabling collaboration and sharing.&lt;/p&gt;
&lt;p&gt;In this post, I will show you how to setup a &lt;strong&gt;production-ready Git &amp;amp; GitHub workflow&lt;/strong&gt; for your project. As you may know, this article is a part of the &lt;a href=&quot;../&quot;&gt;&lt;strong&gt;Flutter Ship&lt;/strong&gt;&lt;/a&gt; series, which guides you in shipping a production-ready Flutter app. However, these principles can be applied to &lt;strong&gt;any language or framework&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;By the end of this article, you will learn the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;How to implement a simple but effective &lt;strong&gt;branching strategy&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;How to adopt a &lt;strong&gt;commit message standard&lt;/strong&gt; for a clean history and automated changelogs.&lt;/li&gt;
&lt;li&gt;How to automatically generate and manage &lt;strong&gt;project changelogs&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;How to use &lt;strong&gt;Git hooks&lt;/strong&gt; to automate local checks, like formating &amp;amp; linting files, linting commits, and running tests.&lt;/li&gt;
&lt;li&gt;How to implement a &lt;strong&gt;GitHub PR Checks&lt;/strong&gt; with &lt;strong&gt;GitHub Actions&lt;/strong&gt; to automate your workflow.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If this sounds like what you&apos;re looking for, let&apos;s dive in!&lt;/p&gt;
&lt;h1&gt;Branch Strategy&lt;/h1&gt;
&lt;p&gt;A clear branching strategy is essential for any software project to prevent the chaos of direct commits to the &lt;code&gt;main&lt;/code&gt; branch. Working directly on &lt;code&gt;main&lt;/code&gt; can lead to frequent merge conflicts, broken builds, and lost code, especially in a team setting.&lt;/p&gt;
&lt;p&gt;The solution is to use short-lived branches for every new piece of work, whether it&apos;s a feature, bugfix, or chore. While there are many branching strategies, I recommend a strategy based on the popular and straightforward &lt;strong&gt;&lt;a href=&quot;https://docs.github.com/en/get-started/using-github/github-flow&quot;&gt;GitHub Flow&lt;/a&gt;&lt;/strong&gt;. It&apos;s simple to understand, promotes continuous delivery, and works exceptionally well for most projects.&lt;/p&gt;
&lt;p&gt;The core idea is illustrated below:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/github-flow.webp&quot; alt=&quot;Github Flow&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Branch Types Explained&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Main Branch (&lt;code&gt;main&lt;/code&gt;)&lt;/strong&gt;: This is the stable branch that always reflects the latest production-ready state of your project. All releases and deployments are made from here.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Feature branches (&lt;code&gt;feature-*&lt;/code&gt; or &lt;code&gt;task-id-*&lt;/code&gt;)&lt;/strong&gt;: Used for developing new features, enhancements, bug fixings or tasks. If you use a task management tool (like Jira), you can integrate with it and name your branch after the task or ticket ID (e.g., &lt;code&gt;ab-123-add-login&lt;/code&gt;). Each feature/task branch is short-lived and merged back into &lt;code&gt;main&lt;/code&gt; via a pull request after review and testing.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This model also supports different development environments with ease. When you complete a feature or task, you open a pull request (&lt;strong&gt;PR&lt;/strong&gt;). You can configure a GitHub Action (or any other CI/CD service) to run tests and automate the generation of &lt;strong&gt;APK&lt;/strong&gt;s for Android and &lt;strong&gt;IPA&lt;/strong&gt;s for iOS, and distribute them to testers or users via your chosen platforms. I’ll cover &lt;em&gt;Flutter Continuous Delivery (CD)&lt;/em&gt; in detail in a future article.&lt;/p&gt;
&lt;p&gt;:::tip[Git &amp;amp; Github Cheat Sheet]
I assume you already know the basics of Git and GitHub, so I won’t include basic Git commands here. If you need a refresher, check out my &lt;a href=&quot;../../git-github-cheat-sheet/&quot;&gt;Git &amp;amp; GitHub Cheatsheet&lt;/a&gt; article.
:::&lt;/p&gt;
&lt;p&gt;:::important[Git Merge Recommendation]
If you merge branches locally, &lt;code&gt;merge --no-ff&lt;/code&gt; is highly recommended. The &lt;code&gt;--no-ff&lt;/code&gt; flag causes the merge to always create a new commit object, even if the merge could be performed with a fast-forward. This avoids losing information about the historical existence of a feature branch and groups all commits that together added the feature.
:::&lt;/p&gt;
&lt;h1&gt;Commit Messages&lt;/h1&gt;
&lt;p&gt;Commit messages are your working history. While the probability of reading them is low, they are still valuable for maintaining a good commit history and understanding changes later on. At least once in a while, you need to read the history to get more details about an emerging issue. Furthermore, in most software projects, the &lt;strong&gt;changelog&lt;/strong&gt; will be generated based on those commit messages to show the release changes between versions. So what could you find or generate if you have the following history?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;* 4dcd0dc fix color
* 1e0acd7 fix issue
* 897cab6 Merge branch &apos;add-comment&apos;
|\  
| * 7b2c3f3 integration
|/  
* 3d0b48d add feature
* 7287225 updated
* 1405829 update post
* dba0ea9 add post
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To have team-level agreed standards for git commit messages, I highly recommend following &lt;strong&gt;&lt;a href=&quot;https://www.conventionalcommits.org&quot;&gt;Conventional Commits&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;type&amp;gt;[optional scope]: &amp;lt;description&amp;gt;

[optional body]

[optional footer(s)]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check out the following example that uses &lt;strong&gt;conventional commit&lt;/strong&gt; messages, which are much easier to understand and allow you to easily generate &lt;strong&gt;changelogs&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;* 4dcd0dc Update themeColor fixed property to true
* 1e0acd7 fix(comment): Update check script to include biome CI
* 897cab6 Merge branch &apos;add-comment&apos;
|\  
| * 7b2c3f3 feat(comment): Integrate Giscus for comments and add styles
|/  
* 8cbd821 fix(dependencies): Update astro and biome versions in pnpm-lock.yaml
* 3d0b48d feat: Add support for rendering mermaid codes in the markdown files
* 7287225 ci: Update code quality job
* 1405829 refactor(post): Update muslim data post and released
* dba0ea9 feat(post): Add the first draft of muslim data post
* 31a1ef8 build: Remove astro compress
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::tip[Commit Structure]
I highly recommend you to read the &lt;strong&gt;&lt;a href=&quot;https://www.conventionalcommits.org&quot;&gt;Conventional Commits&lt;/a&gt;&lt;/strong&gt; documentation to get more information about the commit structure like type, scope, description, body, footer, and breaking changes.
:::&lt;/p&gt;
&lt;h2&gt;AI-Generated Commit Messages&lt;/h2&gt;
&lt;p&gt;There are many tools that can help you write better conventional commits, and there are also AI tools to generate them. I used to test various tools to create commit messages, but recently I started using &lt;strong&gt;GitHub Copilot&lt;/strong&gt; to generate conventional commit messages. By default, Copilot doesn&apos;t generate conventional commit messages, so we need to set a custom prompt in the settings.&lt;/p&gt;
&lt;h3&gt;VSCode Copilot Configuration for Commit Messages&lt;/h3&gt;
&lt;p&gt;If you&apos;re using VS Code with workspace-based settings, you can find the settings file at &lt;code&gt;your-project &amp;gt; .vscode &amp;gt; settings.json&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For user-based settings, navigate to: &lt;code&gt;Settings &amp;gt; Extensions &amp;gt; GitHub Copilot Chat &amp;gt; Commit Message Generation&lt;/code&gt; as attached below:
&lt;img src=&quot;assets/copilot-setting.png&quot; alt=&quot;copilot settings&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After you find the settings, add the following prompt to generate conventional commit messages:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// ...existing settings

&quot;github.copilot.chat.commitMessageGeneration.instructions&quot;: [  
  {
    &quot;text&quot;: &quot;Generate a commit message that MUST follow the Conventional Commits format (feat, fix, docs, style, refactor, test, perf, build, ci, chore, revert). Include a brief description of the change in the subject line (under 72 characters) and a summarized explanation in the body, if necessary. Separate the subject and body with a blank line. Example: &apos;feat(auth): Add forgot password functionality to the login screen&apos; or &apos;fix(products): Fix date format in the product list&apos;.&quot;
  }
]

// ...existing settings
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can update the above prompt based on your needs. Now you can generate it with one click! 🎉&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/generate-commit.gif&quot; alt=&quot;generated commit message&quot; /&gt;&lt;/p&gt;
&lt;p&gt;:::warning[Always Review AI Suggestions]
AI-generated messages are a helpful starting point, but they aren&apos;t always perfect. Always review and refine the message for accuracy and clarity before committing.
:::&lt;/p&gt;
&lt;h3&gt;Alternative Tools&lt;/h3&gt;
&lt;p&gt;Besides &lt;strong&gt;GitHub Copilot&lt;/strong&gt;, here are other popular tools for conventional commits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/commitizen-tools/commitizen&quot;&gt;Commitizen&lt;/a&gt;&lt;/strong&gt;: Interactive CLI tool that helps you to create conventional commits, auto bump versions and auto changelog generation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=vivaxy.vscode-conventional-commits&quot;&gt;Conventional Commits VS Code Extension&lt;/a&gt;&lt;/strong&gt;: GUI helper for VS Code users.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/cocogitto/cocogitto&quot;&gt;Cocogitto&lt;/a&gt;&lt;/strong&gt;: The Conventional Commits toolbox.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/Nutlope/aicommits&quot;&gt;AI Commits&lt;/a&gt;&lt;/strong&gt;: A CLI that writes your git commit messages for you with AI.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;More&lt;/strong&gt;: You can find more on the &lt;a href=&quot;https://www.conventionalcommits.org/en/about/&quot;&gt;conventional commits&lt;/a&gt; about page.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Changelog&lt;/h1&gt;
&lt;p&gt;A &lt;code&gt;CHANGELOG.md&lt;/code&gt; is a way to tell your consumers what has changed between versions. One of the most effective ways to do this is to generate it based on your git commit messages, which contain all your change history. There are many tools for generating changelogs, but one of my favorite tools is &lt;strong&gt;&lt;a href=&quot;https://git-cliff.org&quot;&gt;Git Cliff&lt;/a&gt;&lt;/strong&gt;, which is a highly customizable changelog generator. It only takes three simple steps from installation to generation, as explained below:&lt;/p&gt;
&lt;h2&gt;Setup Git Cliff&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Installation&lt;/strong&gt;: It supports most package managers, so you can find your preferred one &lt;a href=&quot;https://git-cliff.org/docs/installation&quot;&gt;here&lt;/a&gt;. I use macOS, so I installed it via Homebrew:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;brew install git-cliff
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Initialization&lt;/strong&gt;: After installation, navigate to your project and initialize it by running the following command, which creates a file named &lt;code&gt;cliff.toml&lt;/code&gt; for further customization:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;git-cliff --init
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Generate&lt;/strong&gt;: Finally, it&apos;s ready to generate a changelog by running the following command:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;git-cliff -o CHANGELOG.md
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&apos;s all you need to generate a changelog! &lt;a href=&quot;https://github.com/my-prayers/muslim-data-flutter/blob/main/CHANGELOG.md&quot;&gt;Here&apos;s an example&lt;/a&gt; that has been generated by &lt;strong&gt;Git Cliff&lt;/strong&gt;. If you need any further customization, you can simply open and edit the &lt;code&gt;cliff.toml&lt;/code&gt; file.&lt;/p&gt;
&lt;h2&gt;Common Customizations&lt;/h2&gt;
&lt;p&gt;I&apos;ve picked some useful customizations from the &lt;code&gt;cliff.toml&lt;/code&gt; to explain here:&lt;/p&gt;
&lt;h3&gt;1. Update the header &amp;amp; footer description&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;[changelog]
# template for the changelog header
header = &quot;&quot;&quot;
# Changelog\n
All notable changes to this project will be documented in this file.\n
&quot;&quot;&quot;

# template for the changelog footer
footer = &quot;&quot;&quot;
&amp;lt;!-- generated by git-cliff --&amp;gt;
&quot;&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. Support conventional and non-conventional commits&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;[git]
# parse the commits based on https://www.conventionalcommits.org
conventional_commits = true
# filter out the commits that are not conventional
filter_unconventional = true
# process each line of a commit as an individual commit
split_commits = false
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. Customize group ordering&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# regex for parsing and grouping commits
commit_parsers = [                                            # Group Ordering
  { message = &quot;^feat&quot;, group = &quot;&amp;lt;!-- 0 --&amp;gt;🚀 Features&quot; },     # 0 
  { message = &quot;^fix&quot;, group = &quot;&amp;lt;!-- 1 --&amp;gt;🐛 Bug Fixes&quot; },     # 1
  { message = &quot;^doc&quot;, group = &quot;&amp;lt;!-- 3 --&amp;gt;📚 Documentation&quot; }, # 3
  { message = &quot;^perf&quot;, group = &quot;&amp;lt;!-- 4 --&amp;gt;⚡ Performance&quot; },   # 4
  { message = &quot;^refactor&quot;, group = &quot;&amp;lt;!-- 2 --&amp;gt;🚜 Refactor&quot; }, # 2 &amp;lt;- ordered to 2
  { message = &quot;^style&quot;, group = &quot;&amp;lt;!-- 5 --&amp;gt;🎨 Styling&quot; },
  { message = &quot;^test&quot;, group = &quot;&amp;lt;!-- 6 --&amp;gt;🧪 Testing&quot; },
  # ...
]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. Skip specific commit groups&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# regex for parsing and grouping commits
commit_parsers = [
  # ...
  { message = &quot;^perf&quot;, group = &quot;&amp;lt;!-- 4 --&amp;gt;⚡ Performance&quot; },
  { message = &quot;^refactor&quot;, group = &quot;&amp;lt;!-- 2 --&amp;gt;🚜 Refactor&quot; },
  { message = &quot;^style&quot;, group = &quot;&amp;lt;!-- 5 --&amp;gt;🎨 Styling&quot; },
  { message = &quot;^test&quot;, group = &quot;&amp;lt;!-- 6 --&amp;gt;🧪 Testing&quot; },
  { message = &quot;^chore\\(release\\)&quot;, skip = true }, # &amp;lt;-- skipped group
  # ...
]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5. Sort commits by date&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# Organize commits within sections by oldest or newest order
sort_commits = &quot;oldest&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For more advanced customizations, check out the &lt;a href=&quot;https://git-cliff.org/docs/&quot;&gt;Git Cliff documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Git Hooks&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;Git hooks are scripts that automatically execute before or after specific Git actions, like committing, pushing, or merging. They allow you to customize Git&apos;s behavior, automate tasks, enforce policies, and streamline your workflow. &lt;a href=&quot;https://githooks.com&quot;&gt;read more&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We can take advantage of Git hooks to automate the following tasks:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;pre-commit&lt;/strong&gt;: Before a commit is created.
&lt;ul&gt;
&lt;li&gt;Apply &lt;code&gt;dart fix&lt;/code&gt; to automatically fix issues.&lt;/li&gt;
&lt;li&gt;Apply &lt;code&gt;dart format&lt;/code&gt; to reformat staged files.&lt;/li&gt;
&lt;li&gt;Run the Flutter linter to check for analysis issues.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;commit-msg&lt;/strong&gt;: After a commit message is written, but before the commit is created.
&lt;ul&gt;
&lt;li&gt;Validate the commit message to ensure it follows the Conventional Commits standard.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;pre-push&lt;/strong&gt;: Before pushing code to a remote repository.
&lt;ul&gt;
&lt;li&gt;Run all Flutter tests to prevent pushing broken code.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::tip[Keep Git Hooks Fast and Reliable]
Regarding the &lt;strong&gt;pre-push&lt;/strong&gt; hook, if you have a large test suite that makes your &lt;code&gt;git push&lt;/code&gt; command too slow, you can skip this hook and set up a &lt;strong&gt;PR check&lt;/strong&gt; in your CI/CD pipeline as an alternative.
:::&lt;/p&gt;
&lt;h2&gt;The Challenge with Manual Git Hooks&lt;/h2&gt;
&lt;p&gt;While you can set up hooks manually by placing executable scripts (e.g., &lt;code&gt;pre-commit&lt;/code&gt;) in your project&apos;s &lt;code&gt;.git/hooks/&lt;/code&gt; directory, this approach has drawbacks. The &lt;code&gt;.git&lt;/code&gt; directory is not version-controlled, so hooks are not shared across your team. Each developer must set them up manually, and keeping them updated is a maintenance burden. A Git hook manager solves these problems.&lt;/p&gt;
&lt;h2&gt;Git Hook Management with Lefthook&lt;/h2&gt;
&lt;p&gt;There are many hook managers available, but since there isn&apos;t one made specifically for Dart, we&apos;ll use a language-agnostic tool that works with any project: &lt;a href=&quot;https://lefthook.dev&quot;&gt;&lt;strong&gt;Lefthook&lt;/strong&gt;&lt;/a&gt;. It&apos;s fast, powerful, and what I use in my own Flutter projects.&lt;/p&gt;
&lt;p&gt;To get started, install Lefthook using your preferred package manager and run &lt;code&gt;lefthook install&lt;/code&gt; in your project&apos;s root directory. This command creates a &lt;code&gt;lefthook.yaml&lt;/code&gt; configuration file and installs the hooks into your &lt;code&gt;.git&lt;/code&gt; directory.&lt;/p&gt;
&lt;p&gt;:::tip
I highly recommend reading the &lt;a href=&quot;https://lefthook.dev&quot;&gt;Lefthook documentation&lt;/a&gt; before proceeding.
:::&lt;/p&gt;
&lt;h2&gt;Lefthook Configuration&lt;/h2&gt;
&lt;p&gt;Let&apos;s configure &lt;code&gt;lefthook.yaml&lt;/code&gt; to run the tasks we outlined earlier.&lt;/p&gt;
&lt;h3&gt;pre-commit: Fix, Format, and Lint&lt;/h3&gt;
&lt;p&gt;This hook will run three commands in parallel on your staged Dart files before every commit.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pre-commit:
  parallel: true
  commands:
    auto-fix:
      run: dart fix --apply &amp;amp;&amp;amp; git add {staged_files}
    pretty:
      glob: &apos;*.dart&apos;
      run: dart format {staged_files} &amp;amp;&amp;amp; git add {staged_files}
    linter:
      run: flutter analyze {staged_files}

commit-msg:
  commands:
    validate:
      run: &apos;scripts/validate_commit_msg.sh&apos;

pre-push:
  parallel: true
  commands:
    tests:
      run: flutter test
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;commit-msg: Validate Commit Message&lt;/h3&gt;
&lt;p&gt;Validating a commit message against the &lt;strong&gt;Conventional Commits&lt;/strong&gt; specification is too complex for a single-line command, so we&apos;ll use an external script.&lt;/p&gt;
&lt;p&gt;First, create the validation script in this path: &lt;code&gt;scripts/validate_commit_msg.sh&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// filepath: scripts/validate_commit_msg.sh
#!/bin/bash

# Path to the commit message file
COMMIT_MSG_FILE=&quot;.git/COMMIT_EDITMSG&quot;

# Check if the commit message file exists
if [ ! -f &quot;$COMMIT_MSG_FILE&quot; ]; then
  echo &quot;Commit message file does not exist.&quot;
  exit 1
fi

# Read the commit message
COMMIT_MSG=$(cat &quot;$COMMIT_MSG_FILE&quot;)

# Get the first line of the commit message
FIRST_LINE=$(echo &quot;$COMMIT_MSG&quot; | head -n 1)

# Define the Conventional Commits regex pattern
# Scope cannot contain spaces - only letters, numbers, hyphens, and underscores
PATTERN=&apos;^(feat|fix|build|chore|ci|docs|style|refactor|perf|test)(\([a-zA-Z0-9_-]+\))?(!)?: .+&apos;

# Validate the commit message format
if [[ ! &quot;$FIRST_LINE&quot; =~ $PATTERN ]]; then
  echo &quot;👎 Invalid commit message format.&quot;
  echo &quot;Commit message should follow the Conventional Commits format:&quot;
  echo &quot;  &amp;lt;type&amp;gt;(&amp;lt;scope&amp;gt;): &amp;lt;description&amp;gt;&quot;
  echo &quot;  - type: feat, fix, build, chore, ci, docs, style, refactor, perf, test&quot;
  echo &quot;  - scope: optional, no spaces allowed (use hyphens or underscores instead)&quot;
  echo &quot;  - description: brief description&quot;
  echo &quot;&quot;
  echo &quot;Examples:&quot;
  echo &quot;  ✅ feat(user-auth): Add new authentication method&quot;
  echo &quot;  ✅ fix(api_client): Fix connection timeout issue&quot; 
  echo &quot;  ❌ fix(my feature): Fix some bug (scope contains spaces)&quot;
  exit 1
fi

# Check if the first line is no more than 72 characters
if [ ${#FIRST_LINE} -gt 72 ]; then
  echo &quot;👎 Commit message first line is too long (${#FIRST_LINE} characters).&quot;
  echo &quot;Please keep the first line to 72 characters or less.&quot;
  exit 1
fi

echo &quot;👍 Valid commit message!&quot;
exit 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, make the script executable:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;chmod +x scripts/validate_commit_msg.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, add the &lt;code&gt;validate-commit-msg&lt;/code&gt; script to your &lt;code&gt;lefthook.yaml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pre-commit:
  parallel: true
  commands:
    auto-fix:
      run: dart fix --apply &amp;amp;&amp;amp; git add {staged_files}
    pretty:
      glob: &apos;*.dart&apos;
      run: dart format {staged_files} &amp;amp;&amp;amp; git add {staged_files}
    linter:
      run: flutter analyze {staged_files}

commit-msg:
  commands:
    validate:
      run: &apos;scripts/validate_commit_msg.sh&apos;

pre-push:
  parallel: true
  commands:
    tests:
      run: flutter test
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;pre-push: Run Tests&lt;/h3&gt;
&lt;p&gt;This hook runs all Flutter tests before you push. The push will be blocked if any tests fail.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pre-commit:
  parallel: true
  commands:
    auto-fix:
      run: dart fix --apply &amp;amp;&amp;amp; git add {staged_files}
    pretty:
      glob: &apos;*.dart&apos;
      run: dart format {staged_files} &amp;amp;&amp;amp; git add {staged_files}
    linter:
      run: flutter analyze {staged_files}

commit-msg:
  commands:
    validate:
      run: &apos;scripts/validate_commit_msg.sh&apos;

pre-push:
  parallel: true
  commands:
    tests:
      run: flutter test
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After you completed the configuration, you need to rerun the &lt;code&gt;lefthook install&lt;/code&gt; to resync the git hooks. With this setup, your local development workflow is now automated to enforce code quality and consistency.&lt;/p&gt;
&lt;h1&gt;GitHub PR Checks&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;GitHub Actions&lt;/strong&gt; is a continuous integration and continuous delivery (CI/CD) platform that allows you to automate your build, test, and deployment pipeline. You can create workflows that build and test every pull request to your repository, or deploy merged pull requests to production. &lt;a href=&quot;https://docs.github.com/en/actions/get-started/understanding-github-actions&quot;&gt;read more&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;:::tip
If you are new to CI/CD, I highly recommend reading the &lt;a href=&quot;https://docs.github.com/en/actions/get-started/understanding-github-actions&quot;&gt;GitHub Actions&lt;/a&gt; documentation to get familiar with the core concepts.
:::&lt;/p&gt;
&lt;p&gt;Local Git hooks are great for immediate feedback, but they can be bypassed with a simple &lt;code&gt;--no-verify&lt;/code&gt; flag. To guarantee that every commit merged into main follows the &lt;strong&gt;Conventional Commits&lt;/strong&gt; standard, passes &lt;strong&gt;Flutter analyze&lt;/strong&gt; and has no &lt;strong&gt;failing tests&lt;/strong&gt;, we can set up a &lt;strong&gt;GitHub Actions&lt;/strong&gt; workflow to run these validations automatically whenever a &lt;strong&gt;PR&lt;/strong&gt; is opened against the &lt;code&gt;main&lt;/code&gt; branch.&lt;/p&gt;
&lt;p&gt;For the &lt;strong&gt;Flutter analyze&lt;/strong&gt; and &lt;strong&gt;Flutter test&lt;/strong&gt; steps, we need a common Flutter setup. To avoid duplicating this setup code, we can use &lt;a href=&quot;https://docs.github.com/en/actions/tutorials/create-actions/create-a-composite-action&quot;&gt;GitHub Composite actions&lt;/a&gt;. Let&apos;s create a composite action to set up the Flutter environment. First, create a &lt;code&gt;.github/actions/setup-flutter&lt;/code&gt; directory in the root of your project. Inside that directory, create a file named &lt;code&gt;action.yaml&lt;/code&gt; with the following content:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// filepath: .github/actions/setup-flutter/action.yaml
name: &quot;Setup Flutter&quot;
description: &quot;Setup Flutter with dependencies&quot;
runs:
  using: &quot;composite&quot;
  steps:
    - name: Set up Flutter
      uses: subosito/flutter-action@v2
      with:
        channel: stable
        flutter-version-file: pubspec.yaml
    - name: Install dependencies
      run: flutter pub get
      shell: bash
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then let&apos;s create the &lt;strong&gt;PR checks workflow&lt;/strong&gt;. First, create a &lt;code&gt;.github/workflows&lt;/code&gt; directory in the root of your project. Inside that directory, create a file named &lt;code&gt;pull_request.yaml&lt;/code&gt; with the following content:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// filepath: .github/workflows/pull_request.yaml
name: Flutter PR Checks

on:
  pull_request:
    branches:
      - main

jobs:
  commitlint:
    runs-on: ubuntu-latest
    name: Conventional Commitlint
    permissions:
      pull-requests: read
    steps:
      - name: Conventional Commitlint
        uses: opensource-nepal/commitlint@v1

  analyze:
    runs-on: ubuntu-latest
    name: Flutter Analyze
    needs: commitlint
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Setup Flutter
        uses: ./.github/actions/setup-flutter
      - name: Analyze code
        run: flutter analyze

  test:
    runs-on: ubuntu-latest
    name: Flutter Tests
    needs: analyze
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Setup Flutter
        uses: ./.github/actions/setup-flutter
      - name: Run tests
        run: flutter test
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This workflow ensures that every pull request is automatically checked for code quality and passing tests before it can be merged, preventing broken code from reaching your &lt;code&gt;main&lt;/code&gt; branch.&lt;/p&gt;
&lt;h2&gt;More Advanced Checks&lt;/h2&gt;
&lt;p&gt;While our current workflow is robust, you can further enhance code quality and security by enabling additional checks on GitHub.&lt;/p&gt;
&lt;h3&gt;1. Codecov Integration&lt;/h3&gt;
&lt;p&gt;Code coverage metrics help you understand how much of your codebase is tested. &lt;strong&gt;Codecov&lt;/strong&gt; is a popular service that visualizes coverage reports, tracks changes over time, and provides PR feedback about coverage changes.&lt;/p&gt;
&lt;p&gt;To integrate Codecov with your Flutter project, update the &lt;code&gt;test&lt;/code&gt; job in your workflow:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  test:
    runs-on: ubuntu-latest
    name: Flutter Tests
    needs: analyze
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Setup Flutter
        uses: ./.github/actions/setup-flutter
      - name: Run tests with coverage
        run: flutter test --coverage
      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v3
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          file: ./coverage/lcov.info
          fail_ci_if_error: false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After setting this up and connecting your repo to codecov.io, you&apos;ll get detailed coverage reports on each pull request, helping maintain or improve test coverage as your codebase evolves.&lt;/p&gt;
&lt;h3&gt;2. Automated Dependency Management with Dependabot&lt;/h3&gt;
&lt;p&gt;Keeping dependencies up-to-date is crucial for security and accessing new features. GitHub&apos;s &lt;strong&gt;Dependabot&lt;/strong&gt; automates this process by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Security Updates&lt;/strong&gt;: Automatically creating pull requests to update vulnerable dependencies as soon as they are discovered.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Version Updates&lt;/strong&gt;: Regularly checking for new versions of your dependencies and creating PRs to keep them current.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can enable and configure Dependabot by creating a &lt;code&gt;dependabot.yaml&lt;/code&gt; file in your &lt;code&gt;.github&lt;/code&gt; directory. Here&apos;s a basic configuration for a Flutter project:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// filepath: .github/dependabot.yaml
version: 2
updates:
  - package-ecosystem: &quot;pub&quot;
    directory: &quot;/&quot;
    schedule:
      interval: &quot;weekly&quot;
      day: &quot;sunday&quot;
      time: &quot;00:00&quot;
      timezone: &quot;Asia/Baghdad&quot;
    target-branch: &quot;main&quot;
    labels:
      - &quot;dependencies&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a basic configuration example. For more advanced setups, you can &lt;a href=&quot;https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/optimizing-pr-creation-version-updates&quot;&gt;optimize&lt;/a&gt; and &lt;a href=&quot;https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/customizing-dependabot-prs&quot;&gt;customize&lt;/a&gt; Dependabot&apos;s behavior.&lt;/p&gt;
&lt;h3&gt;3. Implement Branch Protection Rules&lt;/h3&gt;
&lt;p&gt;Branch protection rules are a powerful GitHub feature that prevents direct pushes to important branches like &lt;code&gt;main&lt;/code&gt; and enforces quality gates for pull requests. You can configure them in your repository settings under &lt;strong&gt;Settings &amp;gt; Branches &amp;gt; Add branch ruleset&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key rules include&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Require status checks to pass&lt;/strong&gt;: This is essential. It forces your &lt;code&gt;Analyze &amp;amp; Test&lt;/code&gt; workflow (and any others) to succeed before a PR can be merged.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Require a pull request before merging&lt;/strong&gt;: Disallows direct pushes to &lt;code&gt;main&lt;/code&gt; and forces all changes to go through a review process.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Require signed commits&lt;/strong&gt;: Ensures that commits are from a verified source, enhancing security.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By combining your CI workflow with these advanced checks, you create a nearly foolproof system for maintaining a high-quality, secure, and stable codebase.&lt;/p&gt;
&lt;h1&gt;Source Code&lt;/h1&gt;
&lt;p&gt;To see all the concepts from this article implemented in a real Flutter project, check out the &lt;strong&gt;&lt;a href=&quot;https://github.com/kosratdev/simple_todo&quot;&gt;Simple Todo App&lt;/a&gt;&lt;/strong&gt; repository. Switch to the &lt;code&gt;01-git-github-workflow&lt;/code&gt; branch to see all in action.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Clone and explore the implementation
git clone https://github.com/kosratdev/simple_todo.git
cd simple_todo
git checkout 01-git-github-workflow

# Install Lefthook and see the hooks in action
lefthook install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This repository serves as a practical reference for implementing everything we covered in this article. Feel free to fork it and use it as a starting point for your own Flutter projects!&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;We&apos;ve covered a lot of ground in this article, from establishing a solid branching strategy to automating our entire workflow. By implementing &lt;strong&gt;GitHub Flow&lt;/strong&gt;, &lt;strong&gt;Conventional Commits&lt;/strong&gt;, automated &lt;strong&gt;changelogs&lt;/strong&gt; with &lt;strong&gt;Git Cliff&lt;/strong&gt;, local quality checks with &lt;strong&gt;Lefthook&lt;/strong&gt;, and a basic CI pipeline with &lt;strong&gt;GitHub Actions&lt;/strong&gt;, you&apos;ve built a robust foundation for any Flutter project.&lt;/p&gt;
&lt;p&gt;This setup isn&apos;t just about following rules; it&apos;s about creating a predictable, high-quality, and efficient development process. It ensures that every piece of code is consistent, tested, and ready for collaboration, freeing you up to focus on what matters most: building great features.&lt;/p&gt;
&lt;p&gt;This is just the beginning of our journey. In the next article of the &lt;a href=&quot;../&quot;&gt;&lt;strong&gt;Flutter Ship&lt;/strong&gt;&lt;/a&gt; series, we&apos;ll take this foundation and build upon it by setting up different &lt;strong&gt;flavors&lt;/strong&gt; for different environments.&lt;/p&gt;
&lt;p&gt;I hope this guide has been helpful. If you have any questions, suggestions, or want to share your own workflow tips, please leave a comment below.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Happy coding!🚀&lt;/code&gt;&lt;/p&gt;
</content:encoded></item><item><title>Flutter Ship: Production Ready App</title><link>https://kosrat.dev/posts/flutter-ship/</link><guid isPermaLink="true">https://kosrat.dev/posts/flutter-ship/</guid><description>Flutter Ship is a comprehensive series dedicated to guiding Flutter developers through the journey of building production-ready applications.</description><pubDate>Mon, 19 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Overview&lt;/h1&gt;
&lt;p&gt;Welcome to &lt;strong&gt;Flutter Ship&lt;/strong&gt;, a comprehensive series dedicated to guiding Flutter developers through the journey of building production-ready applications.&lt;/p&gt;
&lt;p&gt;Flutter is a powerful cross-platform framework that enables developers to create beautiful, natively compiled applications for mobile, web, and desktop from a single codebase. With its expressive UI, fast development cycles, and growing community support, Flutter has established itself as a significant player in the app development ecosystem.&lt;/p&gt;
&lt;p&gt;Whether you&apos;re building for startups or enterprise-level applications, Flutter provides the tools needed to deliver high-quality experiences across platforms. Companies like Google, Alibaba, and BMW have embraced Flutter for its performance benefits and development efficiency.&lt;/p&gt;
&lt;p&gt;But what exactly makes an app &quot;&lt;strong&gt;production-ready&lt;/strong&gt;&quot;? Is it just about having a functional UI, or does it cover aspects like maintenability, performance optimization, secure architecture, efficient state management, comprehensive testing, and seamless deployment strategies?&lt;/p&gt;
&lt;p&gt;Through this &lt;strong&gt;Flutter Ship&lt;/strong&gt; series, we&apos;ll explore the essential components that transform a basic app into a production-ready solution:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;git-github/&quot;&gt;Flutter Ship 01: Git &amp;amp; GitHub Workflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Flutter Ship 02: Set Up Environment (Flavors, App Icon, Splash Screen, and .env)  --&amp;gt; (&lt;em&gt;Planned&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;Flutter Ship 03: Monitoring &amp;amp; Insights --&amp;gt; (&lt;em&gt;May Be!&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;Flutter Ship 04: Localization --&amp;gt; (&lt;em&gt;May Be!&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;Flutter Ship 05: App Architecture --&amp;gt; (&lt;em&gt;May Be!&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;Flutter Ship 06: &lt;em&gt;More Sections&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Follow along with this series to systematically elevate your Flutter projects from development experiments to polished, production-ready applications ready to delight users around the world.&lt;/p&gt;
&lt;p&gt;Happy Coding!&lt;/p&gt;
</content:encoded></item><item><title>Muslim Data: A library for Islamic Apps</title><link>https://kosrat.dev/posts/muslim-data/</link><guid isPermaLink="true">https://kosrat.dev/posts/muslim-data/</guid><description>Muslim Data is an open-source library for Islamic app developers, providing prayer times, Azkars, Names of Allah, and location services for Android, iOS, and Flutter apps.</description><pubDate>Thu, 24 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;Muslims perform prayers five times a day, and the prayer times vary from one location to another. These times are calculated based on the position of the sun relative to each specific location. The website &lt;a href=&quot;https://praytimes.org&quot;&gt;praytimes.org&lt;/a&gt; explains the prayer time equations in detail and even provides implementations in several programming languages.&lt;/p&gt;
&lt;p&gt;However, not all countries follow these calculated methods. For instance, in Iraq, fixed prayer times have been set by the Ministry of Awqaf, which differ from the auto-calculated ones. Because of this, many prayer time apps do not work correctly in our region.&lt;/p&gt;
&lt;p&gt;Back in 2014, I noticed this gap in our local Muslim community — people couldn’t rely on the available prayer apps due to the use of fixed prayer times. To address this, I started developing a prayer app called &lt;strong&gt;MyPrayers&lt;/strong&gt;. It offers both fixed and auto-calculated prayer times, along with other features such as Azkars, Qibla direction, the 99 Names of Allah, and more. The &lt;strong&gt;MyPrayers&lt;/strong&gt; app is available on &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.kosratdahmad.myprayers&amp;amp;pcampaignid=web_share&quot;&gt;Google Play&lt;/a&gt;, &lt;a href=&quot;https://appgallery.huawei.com/app/C101066833&quot;&gt;AppGallery&lt;/a&gt;, and the &lt;a href=&quot;https://apps.apple.com/us/app/my-prayers/id1390015257&quot;&gt;App Store&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Since then, my goal has been to support developers who want to build Islamic apps by creating reusable and easy-to-integrate packages for &lt;strong&gt;Android&lt;/strong&gt;, &lt;strong&gt;iOS&lt;/strong&gt;, and &lt;strong&gt;Flutter&lt;/strong&gt;. In 2020, I began working on an open-source library called &lt;strong&gt;Muslim Data&lt;/strong&gt;, which provides all the core data needed for a prayer app. This includes accurate prayer times (both fixed and calculated), Azkars, Names of Allah, multi-language support and more — all accessible with just a few lines of code.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Muslim Data&lt;/strong&gt; project is available across all three platforms:&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;my-prayers/muslim-data-android&quot;}
::github{repo=&quot;my-prayers/muslim-data-ios&quot;}
::github{repo=&quot;my-prayers/muslim-data-flutter&quot;}&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Muslim Data&lt;/strong&gt; shipped with a prepopulated database for its core functionality and follows the &lt;code&gt;repository design pattern&lt;/code&gt; which only exposes one class for providing data named &lt;code&gt;MuslimRepository()&lt;/code&gt;. In this article, I’ll briefly showcase the functionality of the Muslim Data package. For more details, feel free to explore the repositories linked above.&lt;/p&gt;
&lt;h2&gt;Location Service&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;Muslim Data&lt;/strong&gt; package has prepopulated database contains some useful location functionalities that helps developers to build an app to not depend on the internet to find location or getting a location details by latitude and longitude. It provides &lt;strong&gt;Offline Location Search&lt;/strong&gt;, &lt;strong&gt;Geocoding&lt;/strong&gt;, and &lt;strong&gt;Reverse Geocoding&lt;/strong&gt; and also the Location object has been used while getting a prayer time in the package.&lt;/p&gt;
&lt;h3&gt;Flutter Sample Usage&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Search for a location&lt;/strong&gt;: You can search for any cities or places around the world and this is useful when a user doesn&apos;t have internet connection or user&apos;s location is turned off so that you can search here:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Future&amp;lt;void&amp;gt; searchLocationExample() async {
  final muslimRepo = MuslimRepository();
  final locations = await muslimRepo.searchLocations(locationName: &apos;makka&apos;);

  print(&quot;Locations: $locations&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Geocode a location&lt;/strong&gt;: Use geocoder method to find a location by country code and city name.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Future&amp;lt;void&amp;gt; geocodeLocationExample() async {
  final muslimRepo = MuslimRepository();
  final location = await muslimRepo.geocoder(
    countryCode: &quot;GB&quot;,
    locationName: &quot;London&quot;,
  );

  if (location != null) {
    print(&quot;Location: $location&quot;);
  } else {
    print(&quot;Location name cannot be geocoded&quot;);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Reverse Geocode a location&lt;/strong&gt;: Use reverseGeocoder method to find a location by latitude and longitude.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Future&amp;lt;void&amp;gt; reverseGeocode() async {
  final muslimRepo = MuslimRepository();
  final location = await muslimRepo.reverseGeocoder(
    latitude: 51.5074,
    longitude: -0.1278,
  );

  if (location != null) {
    print(&quot;Location: $location&quot;);
  } else {
    print(&quot;Location could not be reverse geocoded&quot;);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Prayer Times&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;location&lt;/code&gt; object holds &lt;code&gt;hasFixedPrayerTime&lt;/code&gt; property to indicate whether use auto calculated prayer times or fetch it from the database. So by provide the (&lt;code&gt;Location&lt;/code&gt;, &lt;code&gt;PrayerAttribute&lt;/code&gt;, and &lt;code&gt;Date&lt;/code&gt;) objects to the &lt;code&gt;getPrayerTimes&lt;/code&gt; method, you can easily get prayer times of that location as shown below.&lt;/p&gt;
&lt;h3&gt;Flutter Sample Usage&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Future&amp;lt;void&amp;gt; getPrayerTimesExample() async {
  final muslimRepo = MuslimRepository();

  // Create a PrayerAttribute object.
  final attribute = PrayerAttribute(
    calculationMethod: CalculationMethod.makkah,
    asrMethod: AsrMethod.shafii,
    higherLatitudeMethod: HigherLatitudeMethod.angleBased,
    offset: [0, 0, 0, 0, 0, 0],
  );

  // Assume that &apos;location&apos; has been retrieved using one of the location methods above.

  final prayerTime = await muslimRepo.getPrayerTimes(
    location: location,  
    date: DateTime.now(),
    attribute: attribute,
  );

  if (prayerTime != null) {
    print(&quot;Prayer Times: $prayerTime&quot;);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Azkars&lt;/h2&gt;
&lt;p&gt;As mentioned, the package has been shipped with prepopulated database that contains all Azkars from Hisnul Muslim book with its translation for the given language. Currently, it is available for these languages (&lt;code&gt;en&lt;/code&gt;, &lt;code&gt;ar&lt;/code&gt;, &lt;code&gt;ckb&lt;/code&gt;, &lt;code&gt;ckb_BADINI&lt;/code&gt;, &lt;code&gt;fa&lt;/code&gt;, and &lt;code&gt;ru&lt;/code&gt;).&lt;/p&gt;
&lt;h3&gt;Flutter Example Usage&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Azkar Category&lt;/strong&gt;: Get all azkar categories with its translation for the given language.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void getAzkarCategoriesExample() async {
  final muslimRepo = MuslimRepository();
  final categories = await muslimRepo.getAzkarCategories(language: Language.en);

  print(&quot;Azkar Categories: $categories&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Azkar Chapters&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Get azkar chapters with its translation for the given language.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;void getAzkarChaptersExample() async {
  final muslimRepo = MuslimRepository();
  final categories = await muslimRepo.getAzkarChapters(language: Language.en);

  print(&quot;Azkar Chapters: $categories&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Get azkar chapters for a specific category with its translation for the given language.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;void getAzkarChaptersExample() async {
  final muslimRepo = MuslimRepository();
  final categories = await muslimRepo.getAzkarChapters(
    language: Language.en,
    categoryId: 1,
  );

  print(&quot;Azkar Chapters: $categories&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Get azkar chapters by chapter ids. This method is particularly useful for implementing a favorites feature on azkar. By just saving the azkar ids, you can later retrieve the full details when needed using this method, simplifying management and synchronization of your favorite azkar entries.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;void getAzkarChaptersExample() async {
  final muslimRepo = MuslimRepository();
  final categories = await muslimRepo.getAzkarChaptersByIds(
    language: Language.en,
    chapterIds: [12, 15],
  );

  print(&quot;Azkar Chapters: $categories&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Azkar Items&lt;/strong&gt;: Get azkar items for a specific chapter and it is localized for the given language.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void getAzkarItemsExample() async {
  final muslimRepo = MuslimRepository();
  final categories = await muslimRepo.getAzkarItems(
    language: Language.en,
    chapterId: 1,
  );

  print(&quot;Azkar Items: $categories&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Names of Allah&lt;/h2&gt;
&lt;p&gt;Last but not least, the package provides the 99 names of Allah with its translation, and it is available for these languages (&lt;code&gt;en&lt;/code&gt;, &lt;code&gt;ar&lt;/code&gt;, &lt;code&gt;ckb&lt;/code&gt;, &lt;code&gt;ckb_BADINI&lt;/code&gt;, &lt;code&gt;fa&lt;/code&gt;, and &lt;code&gt;ru&lt;/code&gt;)&lt;/p&gt;
&lt;h3&gt;Flutter Example Usage&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Future&amp;lt;void&amp;gt; getNamesOfAllah() async {
  final muslimRepo = MuslimRepository();
  final names = await muslimRepo.getNames(language: Language.en);
  print(&quot;Names of Allah: $names&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Contribution&lt;/h2&gt;
&lt;p&gt;We welcome contributions from developers of all skill levels! Whether you&apos;re improving documentation, fixing bugs, or suggesting new features, your input is invaluable. To get started, please review our guidelines on GitHub and feel free to open issues or submit pull requests.&lt;/p&gt;
&lt;p&gt;Happy coding! 🚀&lt;/p&gt;
</content:encoded></item><item><title>1M Crashes With Prepopulated Room Database</title><link>https://kosrat.dev/posts/1m-crash-room-database/</link><guid isPermaLink="true">https://kosrat.dev/posts/1m-crash-room-database/</guid><description>Overcoming the challenges of handling 1M+ crashes in the Android Room Database!</description><pubDate>Thu, 04 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;A decade ago, I noticed a gap in our local Muslim community. People couldn&apos;t use the available prayer apps because we used fixed prayer times instead of auto-calculated ones. So, I started developing a prayer app named &lt;strong&gt;MyPrayers&lt;/strong&gt; that offers both fixed and auto-calculated prayer times, Azkars, Qibla direction, Names of Allah, and more. The &lt;strong&gt;MyPrayers&lt;/strong&gt; app has been published on &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.kosratdahmad.myprayers&amp;amp;pcampaignid=web_share&quot;&gt;Google Play&lt;/a&gt;, &lt;a href=&quot;https://appgallery.huawei.com/app/C101066833&quot;&gt;AppGallery&lt;/a&gt;, and the &lt;a href=&quot;https://apps.apple.com/us/app/my-prayers/id1390015257&quot;&gt;App Store&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Since then, I&apos;ve aimed to create an Android and iOS dependency to provide this data with just a few lines of code, supporting anyone who wants to build Muslim apps. In 2020, I started creating an open-source dependency for both Android and iOS platforms named &lt;a href=&quot;https://github.com/kosratdev/muslim-data-android&quot;&gt;&lt;code&gt;muslim-data-android&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://github.com/kosratdev/muslim-data-ios&quot;&gt;&lt;code&gt;muslim-data-ios&lt;/code&gt;&lt;/a&gt;, containing all the data that a prayer app needs.&lt;/p&gt;
&lt;h2&gt;Android Prepopulated Room Database&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;muslim-data-android&lt;/code&gt; uses a local database for the fixed Prayer Times, Names of Allah, and Azkars. As part of the Android Jetpack libraries, Room is recommended to use local Android databases. It also supports prepopulated databases.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Room persistence library provides an abstraction layer over SQLite to allow for more robust database access while harnessing the full power of SQLite.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;From Room Documentation&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;After thoroughly reviewing the Room documentation and various published articles, I came across a useful code snippet for utilizing a prepopulated database with Room. This approach allows us to manage a local prepopulated database within your Android application.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Database(
    entities = [...],
    version = 1,
)
abstract class MuslimDataDatabase : RoomDatabase() {
    internal abstract val muslimDataDao: MuslimDataDao

    companion object {

        @Volatile
        private var INSTANCE: MuslimDataDatabase? = null

        fun getInstance(context: Context): MuslimDataDatabase {
            synchronized(this) {
                var instance = INSTANCE
                if (instance == null) {
                    instance = Room.databaseBuilder(
                        context.applicationContext, MuslimDataDatabase::class.java,
                        &quot;muslim_db.db&quot;
                    )
                        .createFromAsset(&quot;database/muslim_db_v2.0.1.db&quot;)
                        .fallbackToDestructiveMigration()
                        .build()

                    INSTANCE = instance
                }

                return instance
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is very simple and straight forward. It uses a singleton pattern to ensure only one instance of the database exists, using &lt;code&gt;Room.databaseBuilder&lt;/code&gt; to build the database from an asset (&lt;code&gt;muslim_db_v2.0.1.db&lt;/code&gt;) and apply a fallback strategy for destructive migrations. I’ve used it and tested on both different emulators and physical devices. All works as expected without any issues.&lt;/p&gt;
&lt;h2&gt;1M Crash Events 💣💥&lt;/h2&gt;
&lt;p&gt;After successfully publishing and integrating &lt;code&gt;muslim-data-android&lt;/code&gt; in the &lt;strong&gt;MyPrayers&lt;/strong&gt; app, I’ve received some unusual crashes in Firebase Crashlytics. These include &lt;code&gt;SQLiteDiskIOException&lt;/code&gt;, &lt;code&gt;SQLiteReadOnlyDatabaseException&lt;/code&gt;, and &lt;code&gt;SQLiteException&lt;/code&gt;, all originating from the &lt;code&gt;muslim-data-android&lt;/code&gt; dependency.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sample of The Crash&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Fatal Exception: android.database.sqlite.SQLiteDiskIOException: disk I/O error (code 1802 SQLITE_IOERR_FSTAT): , while compiling: PRAGMA journal_mode
at android.database.sqlite.SQLiteConnection.nativePrepareStatement(SQLiteConnection.java)
at android.database.sqlite.SQLiteConnection.acquirePreparedStatement(SQLiteConnection.java:1068)
at android.database.sqlite.SQLiteConnection.executeForString(SQLiteConnection.java:811)
at android.database.sqlite.SQLiteConnection.setJournalMode(SQLiteConnection.java:419)
at android.database.sqlite.SQLiteConnection.setJournalFromConfiguration(SQLiteConnection.java:339)
...
at android.database.sqlite.SQLiteDirectCursorDriver.query(SQLiteDirectCursorDriver.java:46)
at android.database.sqlite.SQLiteDatabase.rawQueryWithFactory(SQLiteDatabase.java:1718)
at android.database.sqlite.SQLiteDatabase.rawQueryWithFactory(SQLiteDatabase.java:1693)
at androidx.sqlite.db.framework.FrameworkSQLiteDatabase.query(FrameworkSQLiteDatabase.kt:156)
at androidx.room.RoomDatabase.query(RoomDatabase.kt:484)
at androidx.room.util.DBUtil.query(DBUtil.kt:75)
at dev.kosrat.muslimdata.database.MuslimDataDao_Impl.getPrayerTimes(MuslimDataDao_Impl.java:196)
at dev.kosrat.muslimdata.repository.MuslimRepository$getPrayerTimes$2.invokeSuspend(MuslimRepository.kt:56)
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Furthermore, some users mentioned that when they close and reopen the app, it crashes. The tricky part is that I couldn’t reproduce it on my end. I’ve carefully reviewed and optimized all the database-related code, including data class entities, DAOs, queries, joins, etc. Despite publishing 3 versions, the issue persists and has reached 1 million crash events, as shown below.
&lt;img src=&quot;1m-crash.png&quot; alt=&quot;1m-crash.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After a long debugging and testing session, I finally discovered what was causing the issue 💪😎. It turns out it&apos;s all related to getting the database instance.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;instance = Room.databaseBuilder(
            context.applicationContext,
            MuslimDataDatabase::class.java,
            &quot;muslim_db.db&quot;
            )
            .createFromAsset(&quot;database/muslim_db_v2.0.1.db&quot;)
            .fallbackToDestructiveMigration()
            .build()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This code snippet recreates the database from the asset every time the app is opened. This can lead to crashes if the database connection is already open. When you close the app, the database singleton instance will be destroyed, but the database connection may not be closed depending on the strategy of the Android OS. When you reopen the app, a crash can occur because it attempts to recreate an already open database.&lt;/p&gt;
&lt;h2&gt;The Right Way of Prepopulating Room Database&lt;/h2&gt;
&lt;p&gt;Unfortunately, none of the articles or Room documentation I’ve read describe this type of issue, even though they prefer to use it that way. Since the root cause of the issue has been detected, I’ve come up with a solution for a prepopulated Room database by using a condition to recreate the database based on its existence.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;...
fun getInstance(context: Context): MuslimDataDatabase {
    synchronized(this) {
        var instance = INSTANCE
        if (instance == null) {
            val dbName = &quot;muslim_db.db&quot;
            val dbBuilder = Room.databaseBuilder(
                context.applicationContext,
                MuslimDataDatabase::class.java,
                dbName
            ).fallbackToDestructiveMigration()

            **// Copy database if it doesn&apos;t exist.
            val dbFile = context.getDatabasePath(dbName)
            if (!dbFile.exists()) {
                dbBuilder.createFromAsset(&quot;database/muslim_db_v2.1.0.db&quot;)
            }**

            instance = dbBuilder.build()

            INSTANCE = instance
        }
        return instance
    }
}
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, this is the right way to use a prepopulated Room database 🎉🎊. It won&apos;t recreate the database from the asset every time the app is opened. When you get an instance from the database, it checks whether the database exists. If it does, it will connect to it; otherwise, it will create the database from the asset.&lt;/p&gt;
&lt;h2&gt;What about Database Migrations?&lt;/h2&gt;
&lt;p&gt;Room database migrations are another thing we need to consider once we have a Room database. The Room documentation explains the migrations in detail, as linked below, except for one scenario: What if I want to migrate my Room database using a prepopulated database from the asset?&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.android.com/training/data-storage/room/migrating-db-versions&quot;&gt;Migrate your Room database  |  Android Developers&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In our specific case, sometimes we need to update prayer times for a city, which involves modifying 366 records at once. It&apos;s not feasible to manage this task using normal migrations, so we must make changes directly to the prepopulated database and then recreate the Room database from the asset.
We need to perform these tasks before getting or while getting the Room instance, similar to the initial database creation. To achieve this, we must identify any database version changes and then recreate it from the asset, as shown below.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;...
if (!dbFile.exists() || isVersionChanged(context)) {
		dbBuilder.createFromAsset(&quot;database/muslim_db_v2.1.0.db&quot;)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;isVersionChanged&lt;/code&gt; method checks for changes in the database version to see if it has been updated. In Room database, versioning is used for migrations, so we can store the version in shared preferences and compare it each time we get a new instance. If it has changed, the method returns true; if not, it returns false. Additionally, we have used &lt;code&gt;fallbackToDestructiveMigration&lt;/code&gt; and provided no migrations, so the Room database will be recreated from the asset in this case.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Here is the &lt;code&gt;isVersionChanged&lt;/code&gt; method&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;private const val DB_VERSION = 1

@Database(
    entities = [...],
    version = DB_VERSION,
)
abstract class MuslimDataDatabase : RoomDatabase() {
    internal abstract val muslimDataDao: MuslimDataDao

    companion object {
				...
				...
		
				private fun **isVersionChanged**(context: Context): Boolean {
						val pref = PreferenceManager.getDefaultSharedPreferences(context)
				    val savedVersion = pref.getInt(&quot;DB_VERSION&quot;, 1)
				    if (savedVersion &amp;lt; DB_VERSION) {
				        pref.edit { putInt(&quot;DB_VERSION&quot;, DB_VERSION) }
				        return true
				    }
				    return false
				}
		}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;All Together&lt;/h2&gt;
&lt;p&gt;The following snippet code shows the right way to use a prepopulated database from the asset and considering data migrations from the asset too.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;private const val DB_VERSION = 1

@Database(
    entities = [...],
    version = DB_VERSION
)
abstract class MuslimDataDatabase : RoomDatabase() {
    internal abstract val muslimDataDao: MuslimDataDao

    companion object {

        @Volatile
        private var INSTANCE: MuslimDataDatabase? = null

        fun getInstance(context: Context): MuslimDataDatabase {
            synchronized(this) {
                var instance = INSTANCE
                if (instance == null) {
                    val dbName = &quot;muslim_db.db&quot;
                    val dbBuilder = Room.databaseBuilder(
                        context.applicationContext,
                        MuslimDataDatabase::class.java,
                        dbName
                    ).fallbackToDestructiveMigration()

                    val dbFile = context.getDatabasePath(dbName)
                    if (!dbFile.exists() || isVersionChanged(context)) {
                        dbBuilder.createFromAsset(&quot;database/muslim_db_v2.1.0.db&quot;)
                    }

                    instance = dbBuilder.build()

                    INSTANCE = instance
                }
                return instance
            }
        }

        private fun isVersionChanged(context: Context): Boolean {
						val pref = PreferenceManager.getDefaultSharedPreferences(context)
				    val savedVersion = pref.getInt(&quot;DB_VERSION&quot;, 1)
				    if (savedVersion &amp;lt; DB_VERSION) {
				        pref.edit { putInt(&quot;DB_VERSION&quot;, DB_VERSION) }
				        return true
				    }
				    return false
				}
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally we fixed the million crash events 🎉🎊💪
&lt;img src=&quot;1m-crash-closed.gif&quot; alt=&quot;1m crash closed&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In wrapping up, using the Room with prepopulated databases in Android applications provides a great way to manage local databases with ease. By following the recommended approach and using the provided code snippets, developers can avoid the crashes that happened to me. Their apps will be safer, more efficient, and more reliable.&lt;/p&gt;
&lt;p&gt;Happy Coding!&lt;/p&gt;
</content:encoded></item><item><title>Git &amp; GitHub Cheat Sheet</title><link>https://kosrat.dev/posts/git-github-cheat-sheet/</link><guid isPermaLink="true">https://kosrat.dev/posts/git-github-cheat-sheet/</guid><pubDate>Tue, 28 May 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Having a &lt;code&gt;Git&lt;/code&gt; and &lt;code&gt;GitHub&lt;/code&gt; cheat sheet is highly beneficial, especially for those who frequently utilize these tools but may not remember every command. Git commands are crucial as they allow us to perform tasks like creating new repositories, making changes to existing projects, and viewing the history of our changes.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;git.gif&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This &lt;strong&gt;cheat sheet&lt;/strong&gt; is a handy reference guide for both beginners and experienced users. For beginners, it provides a simple and quick way to learn and use the most common commands. For experienced users, it serves as a quick reference for less frequently used commands or options. By having all the essential Git and GitHub commands in one place, productivity can be improved and common tasks can be performed faster 👌.&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;Git Configurations&lt;/strong&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# Sets up Git with your name
git config --global user.name &quot;&amp;lt;Your-Full-Name&amp;gt;&quot;

# Sets up Git with your email
git config --global user.email &quot;&amp;lt;your-email-address&amp;gt;&quot;

# Sign all commits by default 
git config --global commit.gpgsign true

# Globally disables fast-forward merging by default
git config --global merge.ff false

# Makes sure that Git output is colored
git config --global color.ui auto

# Displays the original state in a conflict
git config --global merge.conflictstyle diff3

# Configure text editor
git config --global core.editor &quot;[code, atom, vim , nano, gedit] --wait&quot;

# List all your configurations
git config --list
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Git Startup Commands&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# Create brand new repositories (repos) on your computer.
git init

# Copy existing repos from somewhere else to your local computer.
git clone {Repo URL}

# Check the status of a repo
git status
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Git Log&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# Display information about the existing commits.
git log

# Display commits per one line.
git log --oneline

# Display the files that have been changed in the commit.
git log --stat

# display the actual changes made to a file.
git log -p

# display the actual changes made to a commit.
git log -p {commit id}
# Or 
git show {commit id}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Git Add &amp;amp; Commit&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# Add files from the working directory to the staging index.
git add

# add files
git add {file1} {file2} ...

# Add all files in the current directory
git add .

# Take files from the staging index and save them in the repository.
git commit

# Displays the difference between two versions of a file.
git diff
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Refresh gitignore&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# Unstage all files 
git rm -r --cached .

# Add them back in so that, the new .gitignore files will work.
git add .
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Git Tag&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# Add a signed tag
git tag -s v1.0

# Display all tags
git tag

# Delete a tag locally
git tag -d v1.0

# Delete a tag from the remote
git push --delete origin v1.0

# Adding a tag to a past commit
git tag -a v1.0 {commit id}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Git Branch&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# List all branch names in the repository.
git branch

# Create new branch named sidebar.
git branch sidebar

# Change current branch to sidebar.
git checkout sidebar

# Create the alt-sidebar-loc branch and have it point to the commit with SHA 42a69f.
git branch alt-sidebar-loc 42a69f

# Delete a branch.
git branch -d sidebar

# Force delete a branch.
git branch -D sidebar

# see all branches&apos; history at once.
git log --oneline --decorate --graph --all

# Create a branch and then checkout to the new branch.
git checkout -b fix-footer
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Git Merge&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# Merge sidebar into current branch without fast forwarding merge.
git merge --no-ff sidebar
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Git Revert &amp;amp; Reset&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# Alter the most-recent commit or add missing files
git commit --amend

# Reverses given commit
git revert {commit-id}

# Erases commits
git reset
git reset [tags] HEAD~1
tags:
--mix -&amp;gt; move commit to working directory
--soft -&amp;gt; move commit to staged index
--hard -&amp;gt; move commit to trash
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;GitHub&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# Display full path to the remote repository.
git remote -v

# Create a connection from local repository to the remote repository.
git remote add origin {repository link}

# Send changes to the remote
git push

# Retrieve updates from the remote
git pull
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&apos;s all for this Git and GitHub cheat sheet! Keep this guide handy for quick reference while working on your projects. Remember, practice makes perfect, so keep using these commands until they become second nature. &lt;code&gt;Happy coding! 👨‍💻👩‍💻&lt;/code&gt;&lt;/p&gt;
</content:encoded></item></channel></rss>