‘The half minute which we daily devote to the winding-up of our watches is an exertion of labour almost insensible; yet, by the aid of a few wheels, its effect is spread over the whole twenty-four hours.’

Org-roam on Android

Benjamin Slade

I’ve been using the note-taking Zettelkasten-ish Org-roam system for a few months and it’s been very useful to me, just as a low-friction way of making more notes and easily finding and/or (re)discovering notes that I’ve made.

It’s pretty useful to be able to have access to these notes, and be able to quickly add notes, on mobile as well. I thought it might be useful to include here some notes on how to do, since (especially since v2 of Org-roam) there are some hurdles.

On Android/LineageOS install the F-Droid app store, and then from there install Termux. Open Termux and install four things we’ll need (strictly speaking you don’t need curl and ripgrep, but they’ll be useful): Emacs, sqlite, curl, and ripgrep via pkg install emacs sqlite curl ripgrep.

You can then open up Emacs via emacs and get started.

I include here a commented partial version of my ~/.emacs.d/init.el configuration file for my Termux/Emacs Org-roam setup:


;; package setup - bootstrap the package system
(require 'package)
(setq package-enable-at-startup nil)
(setq gnutls-algorithm-priority "NORMAL:-VERS-TLS1.3")
(setq package-archives
      '(("GNU ELPA"     . "https://elpa.gnu.org/packages/")
      ("ORG"		. "https://orgmode.org/elpa/")
        ("MELPA Stable" . "https://stable.melpa.org/packages/")
        ("MELPA"        . "https://melpa.org/packages/"))
      '(("ORG"		. 20)
      ("MELPA"        . 15)
      ("MELPA Stable" . 10)
        ("GNU ELPA"     . 5)))


;; Bootstrap `use-package'
(unless (package-installed-p 'use-package)
  (package-install 'use-package))

  (require 'use-package))

;; for Termux-specific things; useful if you want to share
;; configs across platforms
(defvar termux-p
  (not (null (getenv "ANDROID_ROOT")))
  "If non-nil, GNU Emacs is running on Termux.")

(when termux-p
  (unless (package-installed-p 'use-package)
    (package-install 'use-package)))

;; This makes Emacs in Termux use your Android browser for opening urls
(setq browse-url-browser-function 'browse-url-xdg-open)

;; mouse
;; enable mouse reporting for terminal emulators
;; this lets you scroll around by swiping
(unless window-system
  (xterm-mouse-mode 1)
  (global-set-key [mouse-4] (lambda ()
                              (scroll-down 1)))
  (global-set-key [mouse-5] (lambda ()
                              (scroll-up 1))))

;; ORG
(use-package org
  :ensure t
  :ensure org-plus-contrib
  (setq org-src-fontify-natively t)
  ;; (add-to-list 'auto-mode-alist '("\\.org\\'" . org-mode))
  (define-key org-mode-map (kbd "M-p") 'org-metaup)
  (define-key org-mode-map (kbd "M-n") 'org-metadown)
  (setq org-catch-invisible-edits 'show-and-error)
  (setq org-cycle-separator-lines -1)
  (setq org-return-follows-link t)
  (setq org-export-with-toc 'nil)
  (setq org-startup-folded 'content)
  (setq org-ellipsis "⇣")
  ;; **** use regular android apps to view pdfs & images *****
  (when termux-p
    (add-to-list 'org-file-apps '("\\.pdf\\'" . "termux-open %s"))
    (add-to-list 'org-file-apps '("\\.png\\'" . "termux-open %s"))
    (add-to-list 'org-file-apps '("\\.jpg\\'" . "termux-open %s"))
    (add-to-list 'org-file-apps '("\\.jpeg\\'" . "termux-open %s")))
  ;; needed for <s etc. expansion of code-blocks
  (require 'org-tempo))

;; define our Org-roam user and their email (set to your desired name/email)
(defvar roam-user "Some User"
  "The name of the Org-roam note author.")
(defvar roam-email "roman@mode.org"
  "The public email of that author.")

(setq org-roam-v2-ack t)

;; we need this package for v2 of Org-oram
(use-package emacsql-sqlite3
  :ensure t)

;; If you've replicated my setup; otherwise change to the Termux
;; local path.
(setq org-roam-directory (file-truename "~/Documents/Org/org-roam/"))

;; org-roam
(use-package org-roam
  :ensure t
  (setq org-roam-db-location (file-truename "~"))
  (org-roam-directory (file-truename "~/Documents/Org/org-roam/"))
  :bind (("C-c n l" . org-roam-buffer-toggle)
         ("C-c n f" . org-roam-node-find)
         ("C-c n r" . org-roam-graph)
         ("C-c n i" . org-roam-node-insert)
         ("C-c n c" . org-roam-capture)
         ("C-c n g" . org-id-get-create)
         ;; Dailies
         ("C-c n n" . org-roam-dailies-capture-today)
         ("C-c n d" . org-roam-dailies-goto-today) ; find toDay
         ("C-c n v" . org-roam-dailies-goto-date)
         ("C-c n f" . org-roam-dailies-goto-next-note)
         ("C-c n b" . org-roam-dailies-goto-previous-note))

  ;; this is a chunglak's hack to get sqlite to work on Android with org-roam v2:
  ;; from: https://github.com/org-roam/org-roam/issues/1605#issuecomment-885997237
  (defun org-roam-db ()
    "Entrypoint to the Org-roam sqlite database.
Initializes and stores the database, and the database connection.
Performs a database upgrade when required."
    (unless (and (org-roam-db--get-connection)
                 (emacsql-live-p (org-roam-db--get-connection)))
      (let ((init-db (not (file-exists-p org-roam-db-location))))
        (make-directory (file-name-directory org-roam-db-location) t)
        (let ((conn (emacsql-sqlite3 org-roam-db-location)))
          (emacsql conn [:pragma (= foreign_keys ON)])
          (set-process-query-on-exit-flag (emacsql-process conn) nil)
          (puthash (expand-file-name org-roam-directory)
          (when init-db
            (org-roam-db--init conn))
          (let* ((version (caar (emacsql conn "PRAGMA user_version")))
                 (version (org-roam-db--upgrade-maybe conn version)))
             ((> version org-roam-db-version)
              (emacsql-close conn)
               "The Org-roam database was created with a newer Org-roam version.  "
               "You need to update the Org-roam package"))
             ((< version org-roam-db-version)
              (emacsql-close conn)
              (error "BUG: The Org-roam database scheme changed %s"
                     "and there is no upgrade path")))))))
  (defun org-roam-db--init (db)
    "Initialize database DB with the correct schema and user version."
    (emacsql-with-transaction db
      (emacsql db "PRAGMA foreign_keys = ON") ;; added
      (emacsql db [:pragma (= foreign_keys ON)])
      (pcase-dolist (`(,table ,schema) org-roam-db--table-schemata)
        (emacsql db [:create-table $i1 $S2] table schema))
      (pcase-dolist (`(,index-name ,table ,columns) org-roam-db--table-indices)
        (emacsql db [:create-index $i1 :on $i2 $S3] index-name table columns))
      (emacsql db (format "PRAGMA user_version = %s" org-roam-db-version))))
  ;; end chunglak hack

  ;; If using org-roam-protocol
  (require 'org-roam-protocol))

;; These are my capture templates:
(setq org-roam-capture-templates
      `(("d" "default" plain "%?" :if-new
         (file+head "%<%Y%m%d%H%M%S>-${slug}.org"
                    ,(concat "#+title: ${title}\n"
                             "#+date: %U\n\n"))
         :unnarrowed t)))

(setq org-roam-dailies-directory "~/Documents/Org/org-roam/daily")

(setq org-roam-dailies-capture-templates
      `(("d" "default" entry "* %?" :if-new
         (file+head "%(concat org-roam-dailies-directory \"/%<%Y-%m-%d>.org\")"
                    ,(concat "#+title: %<%Y-%m-%d>" "\n"
                             "#+filetags: :daily_journal:\n\n")))))

;; deft - one way to search Org-roam notes, but not the fastest (see below)
(use-package deft
  :ensure t
  :after org
  ("C-c r d" . deft)
  (deft-recursive t)
  (deft-use-filter-string-for-filename t)
  (deft-default-extension "org")
  (deft-directory "~/Documents/Org/org-roam/"))

;; Here end the basic setup, but....


;; bars seems pointless here, but if you like, don't do this
(menu-bar-mode -1)
(tool-bar-mode -1)

;; You could use a different theme
(use-package cyberpunk-theme
  :ensure t
  (load-theme 'cyberpunk))

;; Spell-checking ;;;
(require 'flymake)
(setq ispell-program-name "hunspell") ; could be ispell as well, depending on your preferences
(setq ispell-dictionary "en_GB") ; this can obviously be set to any language your spell-checking program supports
;; I installed the en_GB ones, but these don't come in Termux by default. To add arbitrary hunspell languages, see:
;; https://www.reddit.com/r/termux/comments/k5o6mp/new_hunspell_dictionaries/?
;; in summary:
;; - how to add new: copy .aff and .dic files in /data/data/com.termux/files/usr/share/hunspell/
;; - where to get new: https://www.freeoffice.com/en/download/dictionaries

(dolist (hook '(org-mode-hook))
  (add-hook hook (lambda () (flyspell-mode 1))))
(add-hook 'org-mode-hook (lambda () (setq ispell-parser 'tex))) ; make orgmode recognise LaTeX syntax [from http://stackoverflow.com/questions/11646880/flyspell-in-org-mode-recognize-latex-syntax-like-auctex ]
(add-hook 'text-mode-hook #'flyspell-mode)

;; Undo-Tree ;; - a new undo package
(use-package undo-tree
  :ensure t
  ;; (setq undo-tree-auto-save-history 1)
  ;; Each node in the undo tree should have a timestamp.
  (setq undo-tree-visualizer-timestamps t)
  ;; Show a diff window displaying changes between undo nodes.
  (setq undo-tree-visualizer-diff t)

;; display time and date in modeline, if you like
(setq display-time-day-and-date t)
(display-time-mode 1)

;; prettier bullets
(use-package org-bullets
  :ensure t
  (add-hook 'org-mode-hook (lambda () (org-bullets-mode 1)))
  (setq org-bullets-bullet-list '("⋇" "∴" "∵" "∷" "∺")))

;; A nice way of quickly adding links.
;; (Though in Termux, you first must paste from your
;; Android clipboard and then copy/kill via Emacs before
;; it'll work.)
(use-package org-cliplink
  :ensure t
  (define-key org-mode-map (kbd "C-c o c") #'org-cliplink))

;; This is also not needed, but adds some (dubiously) useful properties
;; to the Org-roam file's property drawer.

;; First, set up a system for getting location
;; (we could also try to leverage termux's built-in
;; GPS location abilities via `termux-location`, but
;; it seems a bit slow and doesn't even always work if
;; your device can't get a good satellite connection.)
(setq calendar-latitude 0)
(setq calendar-longitude 0)
(defun bms/get-lat-long-from-ipinfo ()
      ((latlong (substring
                 (shell-command-to-string "curl -s 'ipinfo.io/loc'") 0 -1))
       (latlong-list (split-string latlong ",")))
    (setq calendar-latitude (string-to-number (car latlong-list)))
    (setq calendar-longitude (string-to-number (cadr latlong-list)))))

(defun bms/add-other-auto-props-to-org-roam-properties ()
  (unless (file-exists-p (buffer-file-name))
    (unless (org-find-property "CREATION_TIME")
      (org-roam-add-property (format-time-string "%s"
                                                 (nth 5
                                                      (file-attributes (buffer-file-name))))
    (unless (org-find-property "AUTHOR")
      (org-roam-add-property roam-user "AUTHOR"))
    (unless (org-find-property "MAIL")
      (org-roam-add-property roam-email "MAIL"))
    (unless (org-find-property "LAT_LONG")
      (org-roam-add-property (concat (number-to-string calendar-latitude) "," (number-to-string calendar-longitude)) "LAT-LONG"))))

(add-hook 'org-roam-capture-new-node-hook #'bms/add-other-auto-props-to-org-roam-properties)

;; You could use Ivy or Helm or the default, but I
;; like Selectrum, Consult & friends. Plus we can leverage
;; Consult for a nice alternative to deft for note-searching.
;; You'll need this to use my ripgrep note searching feature below.

;; selectrum
(use-package selectrum
  :ensure t
  (selectrum-mode +1))

;; ;; prescient  - T9
(use-package prescient
  :ensure t
  (setq prescient-persist-mode t)
  (setq prescient-filter-method '(literal regexp initialism fuzzy))) ;; added fuzzy

(use-package orderless
  :ensure t
  :init (icomplete-mode)                ; optional but recommended!
  :custom (completion-styles '(orderless))
  (setq orderless-matching-styles '(orderless-flex))
  ;; This means that the company-capf backend will automatically use orderless, but following issue exists:
  ;; Pressing SPC takes you out of completion, so with the default separator you are limited to one component,
  ;; which is no fun. To fix this add a separator that is allowed to occur in identifiers, for example, for
  ;; Emacs Lisp code you could use an ampersand:
  (setq orderless-component-separator "[ &]")

  ;; The matching portions of candidates aren’t highlighted. But while you can’t get different faces for
  ;; different components, you can at least get the matches highlighted in the sole available face with this configuration
  (defun just-one-face (fn &rest args)
    (let ((orderless-match-faces [completions-common-part]))
      (apply fn args)))

  (advice-add 'company-capf--candidates :around #'just-one-face))

(use-package selectrum-prescient
  :ensure t
  ;; to make sorting and filtering more intelligent
  (selectrum-prescient-mode +1)

  ;; Filtering with orderless
  (setq selectrum-refine-candidates-function #'orderless-filter)
  (setq selectrum-highlight-candidates-function #'orderless-highlight-matches)

  ;; If you also configure `completion-styles` for orderless you might want to use the
  ;; following advice because orderless isn't well suited for initial gathering of
  ;; candidates by completion in region.
  (advice-add #'completion--category-override :filter-return
              (defun completion-in-region-style-setup+ (res)
                "Fallback to default styles for region completions with orderless."
                (or res
                    ;; Don't use orderless for initial candidate gathering.
                    (and completion-in-region-mode-predicate
                         (not (minibufferp))
                         (equal '(orderless) completion-styles)
                         '(basic partial-completion emacs22)))))

  ;; Minibuffer-actions with embark

  ;; You should bind embark commands like embark-act, embark-act-noexit
  ;; and embark-export in minibuffer-local-map (as embark commands are not selectrum specific).
  ;; For available commands and other embark configurations see the embark documentation and its wiki.

  (defun current-candidate+category ()
    (when selectrum-is-active
      (cons (selectrum--get-meta 'category)

  (add-hook 'embark-target-finders #'current-candidate+category)

  (defun current-candidates+category ()
    (when selectrum-is-active
      (cons (selectrum--get-meta 'category)
             ;; Pass relative file names for dired.

  (add-hook 'embark-candidate-collectors #'current-candidates+category)
  ;; No unnecessary computation delay after injection.
  (add-hook 'embark-setup-hook 'selectrum-set-selected-candidate)

  ;; The following is not selectrum specific but included here for convenience.
  ;; If you don't want to use which-key as a key prompter skip the following code.

  (setq embark-action-indicator
        (lambda (map) (which-key--show-keymap "Embark" map nil nil 'no-paging)
        embark-become-indicator embark-action-indicator)

  ;; to save your command history on disk, so the sorting gets more
  ;; intelligent over time
  (prescient-persist-mode +1))

;; Example configuration for Consult
(use-package consult
  ;; Replace bindings. Lazily loaded due by `use-package'.
  :bind (("C-x M-:" . consult-complex-command)
         ("C-c h" . consult-history)
         ("C-c m" . consult-mode-command)
         ("C-x b" . consult-buffer)
         ("C-x 4 b" . consult-buffer-other-window)
         ("C-x 5 b" . consult-buffer-other-frame)
         ("C-x r x" . consult-register)
         ("C-x r b" . consult-bookmark)
         ("M-g g" . consult-goto-line)
         ("M-g M-g" . consult-goto-line)
         ("M-g o" . consult-outline)       ;; "M-s o" is a good alternative.
         ("M-g l" . consult-line)          ;; "M-s l" is a good alternative.
         ("M-g m" . consult-mark)          ;; I recommend to bind Consult navigation
         ("M-g k" . consult-global-mark)   ;; commands under the "M-g" prefix.
         ("M-g r" . consult-ripgrep)      ;; or consult-grep, consult-ripgrep
         ("M-g f" . consult-find)          ;; or consult-locate, my-fdfind
         ("M-g i" . consult-project-imenu) ;; or consult-imenu
         ("M-g e" . consult-error)
         ("M-s m" . consult-multi-occur)
         ("M-y" . consult-yank-pop)
         ("<help> a" . consult-apropos))

  ;; The :init configuration is always executed (Not lazy!)

  ;; Custom command wrappers. It is generally encouraged to write your own
  ;; commands based on the Consult commands. Some commands have arguments which
  ;; allow tweaking. Furthermore global configuration variables can be set
  ;; locally in a let-binding.
  (defun my-fdfind (&optional dir)
    (interactive "P")
    (let ((consult-find-command '("fdfind" "--color=never" "--full-path")))
      (consult-find dir)))

  ;; Replace `multi-occur' with `consult-multi-occur', which is a drop-in replacement.
  (fset 'multi-occur #'consult-multi-occur)

  ;; Configure other variables and modes in the :config section, after lazily loading the package

  ;; Configure preview. Note that the preview-key can also be configured on a
  ;; per-command basis via `consult-config'.
  ;; (setq consult-preview-key 'any) ;; any key triggers preview, the default

  ;; Optionally configure narrowing key.
  ;; Both < and C-+ work reasonably well.
  (setq consult-narrow-key "<") ;; (kbd "C-+")
  ;; Optionally make narrowing help available in the minibuffer.
  ;; Probably not needed if you are using which-key.
  ;; (define-key consult-narrow-map (vconcat consult-narrow-key "?") #'consult-narrow-help)

  ;; Optional configure a view library to be used by `consult-buffer'.
  ;; The view library must provide two functions, one to open the view by name,
  ;; and one function which must return a list of views as strings.
  ;; Example: https://github.com/minad/bookmark-view/
  ;; (setq consult-view-open-function #'bookmark-jump
  ;;       consult-view-list-function #'bookmark-view-names)

  ;; Optionally configure a function which returns the project root directory
  ;; (autoload 'projectile-project-root "projectile")
  ;; (setq consult-project-root-function #'projectile-project-root)

;; Optionally add the `consult-flycheck' command.
(use-package consult-flycheck
  :bind (:map flycheck-command-map
              ("!" . consult-flycheck)))

;; Optionally enable richer annotations using the Marginalia package
(use-package marginalia
  :ensure t
  ;; The :init configuration is always executed (Not lazy!)
  ;; Must be in the :init section of use-package such that the mode gets
  ;; enabled right away. Note that this forces loading the package.

(use-package embark
  :ensure t
  ("C-S-a" . embark-act)               ; pick some comfortable binding
  ;; For Selectrum users:
  (defun current-candidate+category ()
    (when selectrum-is-active
      (cons (selectrum--get-meta 'category)

  (add-hook 'embark-target-finders #'current-candidate+category)

  (defun current-candidates+category ()
    (when selectrum-is-active
      (cons (selectrum--get-meta 'category)
             ;; Pass relative file names for dired.

  (add-hook 'embark-candidate-collectors #'current-candidates+category)

  ;; No unnecessary computation delay after injection.
  (add-hook 'embark-setup-hook 'selectrum-set-selected-candidate))

;; org-roam-rg-search - this is a much faster way to search Org-roam notes:
;; requires the Selectrum+Consult setup immediately preceding.
;; Use C-c r r to search notes via consult's ripgrep interface
(defun bms/org-roam-rg-search ()
  "Search org-roam directory using consult-ripgrep. With live-preview."
  (let ((consult-ripgrep "rg --null --ignore-case --type org --line-buffered --color=always --max-columns=500 --no-heading --line-number . -e ARG OPTS"))
    (consult-ripgrep org-roam-directory)))

(global-set-key (kbd "C-c rr") 'bms/org-roam-rg-search)

;; speed-keys - see https://github.com/alhassy/emacs.d#manipulating-sections
(setq org-use-speed-commands t)

;; On an org-heading, C-a goes to after the star, heading markers. To use speed keys, run C-a C-a to get to the star markers.
;; C-e goes to the end of the heading, not including the tags.
(setq org-special-ctrl-a/e t)

;;drag images into orgmode
(use-package org-download
  :ensure t
  (add-hook 'dired-mode-hook 'org-download-enable)
  (global-set-key (kbd "C-c o i") #'org-download-yank)
  (setq org-download-method 'attach))

(defun bms/org-attach-insert-link (&optional in-emacs)
  "Insert attachment from list."
  (interactive "P")
  (let ((attach-dir (org-attach-dir)))
    (if attach-dir
      (let* ((file (pcase (org-attach-file-list attach-dir)
                     (`(,file) file)
                     (files (completing-read "Insert attachment: "
                                             (mapcar #'list files) nil t))))
             (path (expand-file-name file attach-dir))
               (desc (file-name-nondirectory path)))
          (let ((initial-input
                ((not org-link-make-description-function) desc)
                (t (condition-case nil
                       (funcall org-link-make-description-function link desc)
                      (message "Can't get link description from %S"
                               (symbol-name org-link-make-description-function))
                      (sit-for 2)
          (setq desc (if (called-interactively-p 'any)
                         (read-string "Description: " initial-input)
            (org-insert-link nil path (concat "attachment:" desc))))
      (error "No attachment directory exist"))))
(define-key org-mode-map (kbd "C-c o l") #'bms/org-attach-insert-link)

;; in case you want some things not in melpa
;; you'll need it for the remaining things below
(use-package quelpa
  :ensure t)

(use-package quelpa-use-package
  :ensure t)

;; A bit of sugar for the visual appearance of Org syntax
;; Use if you like.
(use-package org-appear
  :ensure t
  :quelpa (org-appear :fetcher github :repo "awth13/org-appear")
  (setq org-hide-emphasis-markers t)
  (add-hook 'org-mode-hook 'org-appear-mode))

;; You don't need this, but it's cool and it does work on Android:
;; see https://github.com/org-roam/org-roam-ui for features
(use-package org-roam-ui
  :ensure t
  :quelpa (org-roam-ui :fetcher github :repo "org-roam/org-roam-ui" :branch "main" :files ("*.el" "out"))
  :after org-roam
  :hook (org-roam . org-roam-ui-mode))

An excellent way of keeping Org notes (and files more generally) in sync between desktop, laptop, and mobile devices is Syncthing. On Android I recommend using the Syncthing-Fork app (via F-Droid), which has various improvements over the default Syncthing app on Android, including better file-access features. (On iOS there is now a third-party solution for syncing via Syncthing: Möbius-Sync. I have no idea how to use Emacs/Org-mode on iOS though, but I recall hearing about some ways of running a Linux shell on iOS like iSH, so possibly there’s some way.)

I have Syncthing sync my Org files to a directory in my main “home” directory on Android Documents/Org and then in Termux created a Documents directory and inside of that directory created a symlink to my actual Org directory via ln -s storage/shared/Documents/Org Org. I’ve found that is easier for allowing Syncthing to have access to the files in order to keep them in sync. (And having my Org files live at ~/Documents/Org in Termux mimics the directory structure on my Linux boxes, which makes lots of things easier in terms of sharing configurations.)

If you have written a response to this, enter your response post's URL below.

Or, you can send a "comment" webmention (it's OK if you don't know what that means). When asked about your website on an IndieAuth login screen, simply type https://commentpara.de.

Markdown Support**bold**, _italics_, ~~strikethrough~~, [descr](link), `monospace`, ```LANG\nline1\nline2\n``` (Yep, multi-line code blocks too, with syntax highlighting!), auto-hyperlinking.

Webmentions #

Comment by Anonymous on Sat Sep 4, 2021 17:45 UTC
Can’t thank enough for this! Ever since I upgraded to V2 of org-roam, my termux setup has been broken and for the life of me I haven’t been able to get it to work. Not sure what I was doing wrong, but copying in your init file has finally got me up and running!!! Also a good few tweaks I’ll be borrowing there too :) Thans again — Nathan