Archive of

A memo: Using GitHub actions to deploy a project to your VPS

Today, I created another workflow file to deploy my side project to the production environment. Here's a simple memo for what I have done.

  1. Setup a new domain name on <cloudflare.com>

  2. Setup a GitHub runner on the target VPS (this step is not really necessary, I can use an existing runner, but then I'll need to cope with coping built result to the target VPS )

    1. Setup the runner as a service, in the runner folder, run
      1. sudo ./svc.sh install
      2. sudo ./svc.sh start
  3. Create a work-flow file in the .github/workflow folder, and set

      on:
        workflow_dispatch
    
  4. Manually run the work flow and deploy the project to the target place
  5. Setup that project as a systemd service so we can easily restart it in the workflow file

For your reference, here the whole workflow file, and as I mentioned before, this article helped me a lot.

name: Deploy to the production env

# Controls when the workflow will run
on:
  workflow_dispatch:

jobs:
  deploy:
    # Our previously created self-hosted runner
    runs-on: [self-hosted, linux, racknerd]

    strategy:
      matrix:
        dotnet: ["8.0.x"]

    # A sequence of tasks that will execute as part of the job
    steps:
      # Checks out repository so our job can access it
      - uses: actions/checkout@v4
      - name: Setup .NET Core SDK ${{ matrix.dotnet-version }}
        uses: actions/setup-dotnet@v4
        with:
          ref: ${{ github.event.inputs.tag }}
          dotnet-version: ${{ matrix.dotnet-version }}

      - name: Install dependencies
        run: dotnet restore

      - name: Build
        run: dotnet build --configuration Release --no-restore

      - name: Publish
        run: dotnet publish -c Release --property:PublishDir=/a-target-folder/HappyNotes.Api

      - name: Replace credentials
        run: |
          pwd
          sed -i "s/password-placeholder/${{ secrets.PRODUCTION_MYSQL_PASSWORD }}/g" /a-target-folder/HappyNotes.Api/appsettings.json
          sed -i "s/symmetric-security-key-placeholder/${{ secrets.PRODUCTION_SYMMETRIC_SECURITY_KEY }}/g" /a-target-folder/HappyNotes.Api/appsettings.json
          sed -i "s/staging-happynotes-api.dev/happynotes-api/g" /a-target-folder/HappyNotes.Api/appsettings.json

      - name: Restart the app
        run: |
          echo $XDG_RUNTIME_DIR
          export XDG_RUNTIME_DIR=/run/user/$(id -u)
          systemctl --user restart HappyNotes.Api.service

How to change package name in flutter? (it is not easy!)

IMHO, using a tool should be the solution.

Adding Auto-Focus to TextFields in Flutter

When building user interfaces in Flutter, it's often desirable to have the keyboard automatically pop up and focus set on a specific TextField when navigating to a new page or rendering a widget. This auto-focus feature can greatly improve the user experience by allowing users to start typing immediately without having to manually tap on the TextField to focus it.

In this blog post, we'll explore how to implement the auto-focus feature for TextFields in Flutter using FocusNodes.

Step 1: Create a FocusNode

The first step is to create a FocusNode instance, which represents the focus state of a particular widget. You can create a FocusNode in the initState method of your StatefulWidget:

late FocusNode myFocusNode;

@override
void initState() {
  super.initState();
  myFocusNode = FocusNode();
}

Step 2: Associate the FocusNode with the TextField

Next, you need to associate the FocusNode with the TextField you want to focus. You can do this by passing the FocusNode to the focusNode property of the TextField:

TextField(
  focusNode: myFocusNode,
  // other properties
)

Step 3: Request Focus on the FocusNode

To set the focus on the TextField, you need to request focus on the FocusNode. The best place to do this is after the widget has been rendered, which you can achieve using the WidgetsBinding.instance.addPostFrameCallback method:

@override
void initState() {
  super.initState();
  myFocusNode = FocusNode();
  WidgetsBinding.instance.addPostFrameCallback((_) {
    myFocusNode.requestFocus();
  });
}

The addPostFrameCallback method ensures that the focus request is made after the widget has been rendered, which is necessary to avoid any potential issues with focus management.

Example Implementation

Here's an example implementation of a StatefulWidget that demonstrates the auto-focus feature for a TextField:

class NewNoteState extends State<NewNote> {
  final TextEditingController _noteController = TextEditingController();
  final FocusNode _noteFocusNode = FocusNode();

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) {
      _noteFocusNode.requestFocus();
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('New Note'),
      ),
      body: Column(
        children: [
          TextField(
            controller: _noteController,
            focusNode: _noteFocusNode,
            keyboardType: TextInputType.multiline,
            // other properties
          ),
          // other widgets
        ],
      ),
    );
  }

  @override
  void dispose() {
    _noteController.dispose();
    _noteFocusNode.dispose();
    super.dispose();
  }
}

In this example, we create a FocusNode called _noteFocusNode and associate it with the TextField. In the initState method, we use WidgetsBinding.instance.addPostFrameCallback to request focus on the _noteFocusNode after the widget has been rendered. This will automatically set the focus on the TextField when the NewNote widget is rendered.

Conclusion

Adding the auto-focus feature to TextFields in Flutter can greatly enhance the user experience by allowing users to start typing immediately without having to manually tap on the TextField to focus it. By creating a FocusNode, associating it with the TextField, and requesting focus on the FocusNode after the widget has been rendered, you can easily implement this feature in your Flutter applications.

Remember to dispose of the FocusNode when it's no longer needed to avoid memory leaks. Additionally, be mindful of potential issues with focus management and use the appropriate methods and callbacks to ensure smooth focus handling in your application.

Handling Back Navigation with PopScope in Flutter

Building Flutter apps? You'll likely face situations where you need to control the back button. Imagine a user editing a note, and you want a confirmation dialog before they accidentally ditch their work!

The old WillPopScope widget used to handle this, but it's deprecated as of Flutter 3.12 (thanks, Android 14's new back gesture!). The new sheriff in town is PopScope, but it doesn't quite let you block the back action directly.

After a couple hours of researching and investigating, Here's the solution to get PopScope to mimic WillPopScope:

  @override
  Widget build(BuildContext context) {
    return PopScope(
      canPop: false,
      onPopInvoked: (bool didPop) async {
        if (!didPop) {
          final navigator = Navigator.of(context);
          if (_noteController.text.isEmpty || (_noteController.text.isNotEmpty && (await _showUnsavedChangesDialog(context) ?? false))) {
            navigator.pop();
          }
        }
      },
      child: Scaffold(
      ...

and the following shows the difference:

   @override
   Widget build(BuildContext context) {
-    return WillPopScope(
-      onWillPop: () async {
-        if (_noteController.text.isNotEmpty) {
+    return PopScope(
+      canPop: false,
+      onPopInvoked: (bool didPop) async {
+        if (!didPop) {
+          final navigator = Navigator.of(context);
+          if (_noteController.text.isEmpty || (_noteController.text.isNotEmpty && (await _showUnsavedChangesDialog(context) ?? false))) {
+            navigator.pop();
+          }
-           final shouldPop = await _showUnsavedChangesDialog(context);
-          return shouldPop ?? false;
         }
-        return true;
       },
       child: Scaffold(

Here's how this solution works:

  1. canPop is set to false, disabling the system back gesture.
  2. In the onPopInvoked callback, it checks if didPop is false (indicating that the pop operation hasn't occurred yet)
  3. Then, it checks if there aren't any unsaved changes _noteController.text.isEmpty, or, although there are unsaved changes (_noteController.text.isNotEmpty), the user answers Yes when asking confirmation.
  4. If any of the conditions are true, it manually triggers the pop operation by calling navigator.pop(). Otherwise, do nothing.

By disabling the system back gesture (canPop: false) and manually triggering the pop operation (navigator.pop()), this solution allows you to perform necessary actions (e.g., showing a confirmation dialog) before the pop operation occurs, effectively replicating the behavior of WillPopScope.

I hope it helps!

How to: move the cursor to the first non-whitespace (non-blank) character on the current line in Vim

  1. use:^ (shift + 6) This moves the cursor to the first non-blank character of the current line.
  2. _ (underscore) This also moves the cursor to the first non-blank character on the same line the cursor is on.

By the way, the 0 command moves to the absolute start of the line, including any leading whitespace.