This guide describes how to build and run the app so you can develop it.
A few points will differ depending on your development platform.
Linux: The instructions below should work fine on Linux, with no additional details to worry about.
macOS:
-
Before starting, upgrade to the latest version of the OS and then to the latest Xcode. It's common for older versions of Xcode, even recent ones, to become unable to build.
-
You'll need GNU coreutils installed, e.g. with
brew install coreutils
.
Windows:
- After installing Git, you'll also need to install
rsync
.
-
For running
yarn install
,tools/run-android
, or any other build commands, use the Git Bash prompt. The Windows Command Prompt isn't supported. -
Or, if you'd like a richer command-line environment and are up for trying a beta install process, you can do your Zulip app development through the WSL
bash
command line. To do that: instead of using the instructions below, see our draft guide for using WSL.
Before starting, install these dependencies if you don't have them:
- Git
- Node.js: use the latest 16.x version, or a later LTS version
- Yarn, latest "classic" (1.x) version
Then, run the commands below in your terminal:
git clone https://github.com/zulip/zulip-mobile
cd zulip-mobile
yarn install
Unlike the Zulip Server project, we use the host machine directly for development, instead of provisioning a VM.
To install the React Native tools, and either Xcode or the Android SDK and Android Studio, follow the helpful instructions from React Native upstream on Setting up the development environment. You want the tab "React Native CLI Quickstart"; the "Expo CLI Quickstart" does not apply.
If you're starting with iOS development, be sure to install
CocoaPods,
as the guide instructs, then rerun yarn
.
Continue those instructions until you can run the Zulip Mobile app with one of the following:
tools/run-android
react-native run-ios
- in Xcode, if on macOS (see iOS tips)
- in Android Studio (see "Android tips", below)
You'll want to be able to use both an emulator and a physical device; but for starting out, just get either one working so you can play with the app.
Once you have it running, look at our debugging tips to help see what's happening in the code. On your first sitting, just get as far as using the Chrome Developer Tools, which is easy to set up and powerful for working on most areas of the app. Later, you might look through the other tools and try some more of them out.
- To set up the Android emulator, follow the heading "Using a virtual device" in the React Native setup instructions. After you set up the emulator in Android Studio, you can start it from the command line.
- To build to a physical Android device, you may also need to install the Android NDK. In Android Studio's SDK Manager, under the SDK Tools tab, select NDK (Side by side) for installation; then click OK or Apply.
- When running on a physical device, if the device has Zulip installed from the Play Store, you may need to uninstall that version first.
- Commands once you've set up:
tools/run-android
- build, then run on an active emulator or USB-connected device. This won't start the emulator automatically.yarn build:android-nokeys
- build APKs in release mode, just skipping Sentry setup (which requires an authentication token), and using your debug keystore for signing. The output APKs will be inandroid/app/build/outputs/apk/release/
.yarn build:android-nokeys -Psigned
- build APKs in release mode, just skipping Sentry setup (which requires an authentication token). The output APKs will be inandroid/app/build/outputs/apk/release/
.
More wrinkles are involved; see our separate doc on iOS tips.
This step is optional -- for most development you can use chat.zulip.org, or another live Zulip community you belong to. But sometimes when debugging interactions with the server, or developing server-side changes related to the mobile app, it's helpful to run the mobile app against a development server which you control.
Setting this up involves a few steps, but it should be straightforward if you follow the right instructions carefully. Take a look at our detailed instructions and try them out. If they don't work, please report it in chat, with details on exactly what you did and what happened; we'll help you debug, and then adjust the instructions so they work for the next person with a setup like yours.
Apart from the steps mentioned below, you may find the React Native troubleshooting docs to be helpful.
This should only happen when building old versions of the app, from before PR #3504.
As mentioned there, these errors aren't critical. This is a testing framework which was never in our main build and test workflows, as we never wrote more than a very small smoke-test for it. Ignore the error and carry on.
On macOS, when running yarn install
, you may see an error like this
(reformatted for readability):
[!] CocoaPods could not find compatible versions for pod "Sentry":
In snapshot (Podfile.lock):
Sentry (= 6.1.4)
In Podfile:
RNSentry (from `../node_modules/@sentry/react-native`) was resolved to 2.2.1, which depends on
Sentry (= 6.1.4)
None of your spec sources contain a spec satisfying the dependencies:
`Sentry (= 6.1.4), Sentry (= 6.1.4)`.
You have either:
* out-of-date source repos which you can update with `pod repo update`
or with `pod install --repo-update`.
* mistyped the name or version.
* not added the source repo that hosts the Podspec to your Podfile.
(The error might involve the same pod "Sentry" as in this example, or some other pod.)
To fix the problem, run pod repo update
. Then rerun yarn install
.
The cause of the issue is that CocoaPods needs to download some pods
from the central "CocoaPods Trunk" repository; it caches locally some
metadata about what package versions are available at that repository;
and those caches can get stale and it doesn't automatically
notice. When that happens, CocoaPods disbelieves that any newer
versions exist. Running pod repo update
causes it to refresh those
caches so that CocoaPods sees the newer package versions and can
successfully download and install them.
On Windows, when running tools/run-android
, you may see an error
like this:
> Task :app:buildStaticWebviewAssets FAILED
32 actionable tasks: 3 executed, 29 up-to-date
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':app:buildStaticWebviewAssets'.
> Process 'command 'bash'' finished with non-zero exit value 127
To fix the problem, install rsync
as mentioned
in the "Windows" section above.
When building for iOS, you may see the build fail with a C++ compiler error, perhaps like this:
In file included from /Users/greg/z/mobile/node_modules/react-native/ReactCommon/jsi/jsi/JSIDynamic.cpp:7:
In file included from /Users/greg/z/mobile/node_modules/react-native/ReactCommon/jsi/jsi/JSIDynamic.h:9:
In file included from /Users/greg/z/mobile/ios/Pods/Folly/folly/dynamic.h:717:
In file included from /Users/greg/z/mobile/ios/Pods/Folly/folly/dynamic-inl.h:21:
/Users/greg/z/mobile/ios/Pods/Folly/folly/Conv.h:38:10: fatal error: 'double-conversion/double-conversion.h' file not found
#include <double-conversion/double-conversion.h> // V8 JavaScript implementation
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.
When you see an iOS build failure, use git status
(or
git diff -a ios/Podfile.lock
) to check whether the file
ios/Podfile.lock
has any changes you didn't intentionally make.
In particular, it may have changes to one or more spec checksums,
like this:
SPEC CHECKSUMS:
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
- DoubleConversion: cf9b38bf0b2d048436d9a82ad2abe1404f11e7de
+ DoubleConversion: cde416483dac037923206447da6e1454df403714
This means that CocoaPods has ended up with a different version of the
given dependency from the one we specify in Podfile.lock
. That in
turn can cause the build to fail when the version it gets doesn't
match our other dependencies.
To fix the problem, run rm -rf "ios/Pods/Local Podspecs"
.
Then rerun yarn install
.
The cause of the issue is that CocoaPods trusts the version number in the metadata inside a given pod artifact, and assumes that different versions of a given pod will always have different version numbers; and React Native, in particular, does not reliably satisfy that assumption in its internal pods.
When running the app, you might see in the output of the Metro bundler
-- aka "the JS server", or react-native start
-- an error like this
(reformatted for readability):
error: bundling failed: Error: Unable to resolve module `lodash.union`
from `.../zulip-mobile/src/chat/chatReducer.js`:
Module `lodash.union` does not exist in the Haste module map
This can happen when new dependencies have been added to
package.json
. In the example above, lodash.union
was added.
To fix the problem, run yarn
, which will update your installed
packages in node_modules/
to match the current package.json
. You
might need to restart Metro / react-native start
after doing so.
Build failure: java.nio.file.NoSuchFileException: /Users/chrisbobbe/dev/zulip-mobile/android/app/build/intermediates/
When trying to build the Android app, you may see this error:
java.nio.file.NoSuchFileException: /Users/chrisbobbe/dev/zulip-mobile/android/app/build/intermediates/external_file_lib_dex_archives/debug/out
at sun.nio.fs.UnixException.translateToIOException(UnixException.java:86)
at sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:102)
at sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:107)
at sun.nio.fs.UnixFileSystemProvider.newDirectoryStream(UnixFileSystemProvider.java:407)
Try removing android/.gradle
, running tools/gradle clean
,
and building again.
When trying to build the Android app, you may see this error:
Execution failed for task ':app:packageDebug'.
> A failure occurred while executing com.android.build.gradle.internal.tasks.Workers$ActionFacade
> No file known for: classes.dex
Try removing android/.gradle
, running tools/gradle clean
,
and building again.
When trying to build the Android app, you may see this error:
A problem occurred evaluating project ':@react-native-community_async-storage'.
> java.lang.UnsupportedClassVersionError: com/android/build/gradle/LibraryPlugin : Unsupported major.minor version 52.0
This can happen if you have a very old version of the JDK (Java Development Kit); React Native requires at least JDK 8, which was released in 2014.
To fix this, install a newer JDK. As of 2019, we generally use JDK
11 (which is expected to be the
latest LTS version until 2021). You can check what version is
installed by running the command java -version
; with JDK 8, the
version number starts with "1.8", and with JDK 11, it starts with
"11.".
When trying to build the Android app, you may see this error:
* Where:
Build file '/Users/jappleseed/dev/zulip-mobile/node_modules/@unimodules/react-native-adapter/android/build.gradle'
* What went wrong:
Could not compile build file '/Users/chrisbobbe/dev/zulip-mobile/node_modules/@unimodules/react-native-adapter/android/build.gradle'.
> startup failed:
General error during semantic analysis: Unsupported class file major version 57
java.lang.IllegalArgumentException: Unsupported class file major version 57
at groovyjarjarasm.asm.ClassReader.<init>(ClassReader.java:184)
at groovyjarjarasm.asm.ClassReader.<init>(ClassReader.java:166)
at groovyjarjarasm.asm.ClassReader.<init>(ClassReader.java:152)
at groovyjarjarasm.asm.ClassReader.<init>(ClassReader.java:273)
[...]
This can sometimes happen if you're using JDK 13 to invoke the build
command (e.g., when calling tools/run-android
, or
tools/test native
, or
tools/gradle :app:assembleDebug
). You can check the
version by running java -version
. It seems that upgrading to
macOS 10.15 Catalina automatically upgrades Java to 13.
Somehow, JDK 8 is getting involved, and trying to use classfiles that
it doesn't understand because they were created with JDK 13. We're not
sure how JDK 8 is getting involved; it still seems to be involved
after locating and killing some Gradle-related processes with
ps auxwww | grep gradle
that were using a JDK 8 installation that's
bundled with Android Studio (yours, if you have one, might be at
"/Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/jre/bin/java").
A solution, if you can't find why JDK 8 is getting involved and prevent that, is to ensure those classfiles are also created with JDK 8. (JDK 11 might also work; we haven't tested this.)
- First, find a JDK installation. When you check a Java
version, note that the version number will start with "1.8" for
JDK 8.
- As noted above, you may have a JDK installation included in
Android Studio at a path like the one above. Check its version
by running, e.g.,
"/Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/jre/bin/java" -version
. - Otherwise, on Ubuntu or Debian, you can install it with
sudo apt install openjdk-8-jdk
. - In general, you can download it from Oracle.
- As noted above, you may have a JDK installation included in
Android Studio at a path like the one above. Check its version
by running, e.g.,
- Find a path to that installation to set
JAVA_HOME
to. (This is used bygradlew
to find thejava
command to run Gradle under.)- If you used the installation included in Android Studio, take that path minus the "/jre/bin/java" at the end, e.g., "/Applications/Android Studio.app/Contents/jre/jdk/Contents/Home".
- If you installed the Debian/Ubuntu package, then
ls -l /usr/lib/jvm/
should listjava-8-openjdk-amd64
or something similar to that.
- Using the path from step 2, prefix your build command with
JAVA_HOME=[that path]
(for example,JAVA_HOME=... android/gradlew ...
), and see if that works. - If it does, you can run
export JAVA_HOME=[that path]
by itself, so future build commands in the same terminal window will be able to use it. You can also add that line to your~/.bashrc
or equivalent so you don't have to repeat it in new terminal windows.
You can read the debugging conversation around here.
Build failure: Unable to make field private final java.lang.String java.io.File.path accessible: module java.base does not "opens java.io" to unnamed module @739914dc
This odd-looking error can apparently happen if you're using a too-recent
JDK version, such as 16, to invoke the build command. Try setting
JAVA_HOME
to a path to a JDK 11 installation, using steps 1-4 in the
troubleshooting entry above that also mentions JAVA_HOME
.
When trying to build the Android app, you may see this error:
* What went wrong:
Task 'installDebug' not found in project ':app'.
This (somewhat misleading) error message can occur when the Android
SDK is not fully configured. Creating a new React Native project (per
the upstream docs), and running react-native android
there, may give clearer error messages for debugging.
When trying to build the Android app, if you get an error like this:
> Task :react-native-screens:compileDebugJavaWithJavac FAILED
C:\Users\[...]\zulip-mobile\node_modules\react-native-screens\android\src\main\java\com\swmansion\rnscreens\LifecycleHelper.java:3: error: package androidx.lifecycle does not exist
import androidx.lifecycle.Lifecycle;
^
[... lots more ...]
or like this:
[... lots of output ...]
[...]/MainApplication.java:45: error: constructor RNNotificationsPackage in class RNNotificationsPackage cannot be applied to given types;
new RNNotificationsPackage(),
^
required: Application
found: no arguments
reason: actual and formal argument lists differ in length
[...]
> Task :app:compileDebugJavaWithJavac FAILED
[...]
or, when trying to build the iOS app, if you get several errors like this:
Multiple commands produce '/Users/jappleseed/Library/Developer/Xcode/DerivedData/ZulipMobile-gdifomwwvfiwepckshlwoludvdct/Build/Products/Debug-iphonesimulator/ZulipMobile.app/Fontisto.ttf':
1) Target 'ZulipMobile' (project 'ZulipMobile') has copy command from '/Users/jappleseed/dev/zulip-mobile/node_modules/react-native-vector-icons/Fonts/Fontisto.ttf' to '/Users/jappleseed/Library/Developer/Xcode/DerivedData/ZulipMobile-gdifomwwvfiwepckshlwoludvdct/Build/Products/Debug-iphonesimulator/ZulipMobile.app/Fontisto.ttf'
2) That command depends on command in Target 'ZulipMobile' (project 'ZulipMobile'): script phase “[CP] Copy Pods Resources”
then check that git status
shows you're running unmodified code from
our repo. These errors can be caused by the modifications made if you
run the command react-native link
.
Some React Native projects reportedly say in their build instructions to run this command before building. This is a bad practice: the command edits the project's source files in heuristic ways, and so it should always be a development step -- with the results committed to the project's version-control repo -- and not a build step. We don't use it as a build step.
If you've run react-native link
, you can discard the edits it made
(along with any other edits you've made!) by running
git reset --hard
.
When trying to build or run the app on Windows, you may see this error:
> Task :app:buildDebugStaticWebviewAssets FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':app:buildDebugStaticWebviewAssets'.
> A problem occurred starting process 'command 'bash''
This happens if you try to run the build from the Windows Command
Prompt. Instead, when running any command that runs our code, like
tools/run-android
or tools/test
, use the Git Bash prompt to
run the command.
When trying to build the iOS app, if you get an Xcode error like this (edited slightly for readability):
error: Build input file cannot be found:
'[...]/zulip-mobile/node_modules/react-native/third-party/double-conversion-1.1.6/src/bignum.cc'
(in target 'double-conversion' from project 'React')
then try restarting Xcode.
When trying to build the iOS app, if you get an error like this (in
e.g. the tools/ios build
log file; edited slightly for readability):
error: No signing certificate "iOS Development" found:
No "iOS Development" signing certificate matching team ID "66KHCWMEYB"
with a private key was found. (in target 'ZulipMobile' from
project 'ZulipMobile')
then a possible root cause of the missing certificate is that the team's Apple Developer Program membership needs to be renewed. Once the membership is fixed, the certificate should be generated automatically when needed.
To inspect what certificates exist, see Xcode > Settings > Accounts. When all is well, there should be a cert of type "Apple Development" in the list of certs associated with the team. (The reference in the error message to "iOS Development" corresponds to an alternative type which was the normal one before ca. 2020 and is still accepted.)
Despite the reference to development, this error can appear when attempting a build for release.
When trying to run the app (with e.g. react-native run-ios
), if you
get an error like this (reformatted for readability):
ReferenceError: SHA-1 for file
/usr/local/lib/node_modules/react-native/node_modules/metro/src/lib/polyfills/require.js
(/usr/local/lib/node_modules/react-native/node_modules/metro/src/lib/polyfills/require.js)
is not computed
then the cause may be that your node_modules/
directory isn't
correctly built. This can happen if you try to use npm
to install
the dependencies. We use Yarn, and you should always use yarn
(or
yarn install
, which means the same thing) and not npm
.
To fix the error, run rm -rf node_modules
and then yarn
.
If you're developing on a Linux machine, and when you start the dev version of the app (either in an emulator or on a device) you just get a blank white screen, you may have hit your system's limit for inotify watches. You can increase this limit with the following commands:
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
After running tools/run-android
or react-native run-ios
, if
you see an error like this on the device:
Unable to load script. Make sure you're either running a Metro server (run 'react-native start') or that your bundle 'index.android.bundle' is packaged correctly for release.
then this means you are not running the Metro bundler.
Starting from React Native 0.60, you need to launch the Metro bundler server
separately, using react-native start
. Once the server starts up, run
tools/run-android
again, and the app should not crash.
After starting a debug version of the app, on iOS, while debugging in the Chrome DevTools, you may find the app stays on the loading screen (with the Zulip-purple background, and a progress spinner around the Zulip logo) indefinitely.
If you weren't making use of the Chrome debugging features, you can avoid this issue by simply turning that back off.
Alternatively, you can get past the issue by tapping on the screen about five or ten times over the course of a second or two.
See chat discussion: start, and workaround. This may go away when we switch to Hermes (#5313), where the debugger's architecture is simpler and less likely to affect the running of the app.
This should only happen when building old versions of the app, from before we upgraded to React Native 0.59.10 in commit dfbdd971b in 2019-07.
When trying to run the iOS app, if you get a red error screen with this error message (edited slightly for readability):
Unknown argument type '__attribute__' in method
-[RCTAppState getCurrentAppState:error:].
Extend RCTConvert to support this type.
then you've encountered an incompatibility between Xcode 11 and older versions of React Native.
To fix this, take a look at the upstream
commit
that fixed the issue, and apply that one-line diff directly to the
file node_modules/react-native/React/Base/RCTModuleMethod.mm
.
When running tests (with e.g. tools/test
), you may see an error like
this (edited slightly for readability):
FAIL src/session/__tests__/sessionReducer-test.js
● Test suite failed to run
Jest encountered an unexpected token
This usually means that you are trying to import a file which Jest
cannot parse, e.g. it's not plain JavaScript. [...]
import('react-native-document-picker'));case 2:[...]
^^^^^^
SyntaxError: Unexpected token import
[...]
at ScriptTransformer._transformAndBuildScript
(node_modules/@jest/transform/build/ScriptTransformer.js:471:17)
at ScriptTransformer.transform
(node_modules/@jest/transform/build/ScriptTransformer.js:513:25)
at Object.<anonymous>
(src/compose/ComposeBox.js:30:43)
Typically when this happens it'll be repeated many times, across many of our Jest unit tests.
This can happen if you're using an older version of Node, such as Node 8. (Probably this means our Jest config doesn't have Babel set up quite right. Discussion here.)
To fix this, use a current version of Node instead (the one
recommended in our setup instructions at the top of this page.) You
can check what version is installed by running the command
node --version
.
If you are having issues running the code on your machine, either for the first time or after updating an outdated code with the latest, in some cases it can help to delete the installed dependencies and install them again from scratch:
rm -rf node_modules
yarn cache clean
yarn install
and then retry building and running.
Sometimes, the build cache from previous builds can cause issues, and cleaning it can help:
tools/gradle clean
Optionally, reset iOS simulator:
iOS Menu > Simulator > Reset Content and Settings…
If you get other iOS build failures and you haven't changed anything
in the ios
folder yourself, and you're on the latest version of
Xcode, there might be residue from a previous build interfering in
this one. So, try cleaning the build folder, following the
instructions at iOS tips