React Native enables you to build Android applications that look and feel native with only JavaScript. In this instance, React Native takes care of managing UI state and synchronizing it with the models. And luckily for us, we can use Couchbase Lite to add sync and persistence to a React Native app. In this tutorial, you will learn how to build a simple application to save documents and replicate them to Pasarela de sincronización. Here are the core concepts you will learn:
- Create a basic project with Couchbase Lite Android and Couchbase Lite Java Listener
- Integrating React Native in your project
- Adding Models and UI components with JavaScript
- Setting up Couchbase Sync Gateway
Here’s a sneak peek of what you are going to build:
You can download the completed project from GitHub.
Requisitos previos
- Node.js 4.0 or higher
- Android Studio and an emulator
Primeros pasos
In this section, you will create a new Android Studio project from scratch and integrate React Native to it.
New Android Studio Project
Before you start writing some JavaScript you need to create a new Android Studio project with all the dependencies. Open Android Studio and from the welcome screen select Nuevo proyecto. In the New Project window, enter TodoLite ReactNative Android for the application name and todolite-reactnative-android for the folder name:
Set the minimum required SDK to API 16: Android 4.1 or later and use the currently recommended Android API. After you fill in the fields, the New Project window should look something like this:
Click Next, and choose the Actividad en blanco plantilla:
Haga clic en Acabado and you should see the following in the project navigator:
Bundling dependencies
Expand the app folder, and then open the build.gradle file. Make sure you open the one located in the aplicación folder (also called the module) and add the following in the android sección:
1 2 3 4 5 6 7 |
// solución para el problema de "archivos duplicados durante el empaquetado del APK // véase https://groups.google.com/d/msg/adt-dev/bl5Rc4Szpzg/wC8cylTWuIEJ packagingOptions { excluir META-INF/ASL2.0 excluir META-INF/LICENCIA excluir META-INF/NOTICE } |
Next, open build.gradle at the root (also referred to as the project level gradle file) and add a reference to the Couchbase Maven repository:
1 2 3 4 5 6 7 8 |
allprojects { repositorios { jcenter() maven { url "http://files.couchbase.com/maven2/" } } } |
Now, add the following lines to the top-level dependencias sección:
1 2 3 4 5 6 7 8 9 10 |
dependencias { compilar fileTree(dir: 'libs', incluir: ['*.jar']) testCompile 'junit:junit:4.12' compilar 'com.android.support:appcompat-v7:23.1.0' compilar com.couchbase.lite:couchbase-lite-android:1.1.0 compilar 'com.couchbase.lite:couchbase-lite-java-listener:1.1.0' compilar 'com.couchbase.lite:couchbase-lite-java-javascript:1.1.0' compilar 'com.facebook.react:react-native:0.13.0' } |
In the Android Studio tool bar, click Sync Project with Gradle Files.
Setting up Couchbase Lite and the Listener
Abrir AndroidManifest.xml located in app/src/main and add the permissions:
1 2 3 |
The React Native Android Activity
You need to add some native code in order to start the React Native runtime and get it to render something. Replace the content of MainActivity.java with the following and we’ll explain what is going on next:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
público clase Actividad principal extiende Actividad implementa DefaultHardwareBackBtnHandler { privado ReactRootView mReactRootView; privado ReactInstanceManager mReactInstanceManager; privado final Cadena TAG = "TodoLite"; privado estático final int DEFAULT_LISTEN_PORT = 5984; privado int listenPort; privado Credenciales allowedCredentials; @Anular protegido void onCreate(Paquete savedInstanceState) { super.onCreate(savedInstanceState); Registro.d(TAG, "onCreate method called"); // 1 mReactRootView = nuevo ReactRootView(este); mReactInstanceManager = ReactInstanceManager.constructor() .setApplication(getApplication()) .setBundleAssetName("index.android.bundle") .setJSMainModuleName("index.android") .addPackage(nuevo MainReactPackage()) .setUseDeveloperSupport(BuildConfig.DEBUG) .setInitialLifecycleState(LifecycleState.RESUMED) .construya(); mReactRootView.startReactApplication(mReactInstanceManager, "TodoLite-ReactNative-Android", null); setContentView(mReactRootView); initCBLite(); } privado void initCBLite() { pruebe { // 2 allowedCredentials = nuevo Credenciales("", ""); // 3 Ver.setCompiler(nuevo JavaScriptViewCompiler()); // 4 AndroidContext contexto = nuevo AndroidContext(este); Director.enableLogging(Registro.TAG, Registro.VERBOSE); Director.enableLogging(Registro.TAG_SYNC, Registro.VERBOSE); Director.enableLogging(Registro.CONSULTA_ETIQUETA, Registro.VERBOSE); Director.enableLogging(Registro.TAG_VIEW, Registro.VERBOSE); Director.enableLogging(Registro.TAG_CHANGE_TRACKER, Registro.VERBOSE); Director.enableLogging(Registro.TAG_BLOB_STORE, Registro.VERBOSE); Director.enableLogging(Registro.TAG_DATABASE, Registro.VERBOSE); Director.enableLogging(Registro.TAG_LISTENER, Registro.VERBOSE); Director.enableLogging(Registro.TAG_MULTI_STREAM_WRITER, Registro.VERBOSE); Director.enableLogging(Registro.TAG_REMOTE_REQUEST, Registro.VERBOSE); Director.enableLogging(Registro.TAG_ROUTER, Registro.VERBOSE); Director director = nuevo Director(contexto, Director.OPCIONES_POR_DEFECTO); // 5 listenPort = startCBLListener(DEFAULT_LISTEN_PORT, director, allowedCredentials); Registro.i(TAG, "initCBLite() completed successfully with: " + Cadena.formato( "http://%s:%s@localhost:%d/", allowedCredentials.getLogin(), allowedCredentials.getPassword(), listenPort)); } captura (final Excepción e) { e.printStackTrace(); } } privado int startCBLListener(int listenPort, Director director, Credenciales allowedCredentials) { LiteListener oyente = nuevo LiteListener(director, listenPort, allowedCredentials); int boundPort = oyente.getListenPort(); Hilo hilo = nuevo Hilo(oyente); hilo.iniciar(); devolver boundPort; } @Anular protegido void onPause() { super.onPause(); si (mReactInstanceManager != null) { mReactInstanceManager.onPause(); } } @Anular protegido void onResume() { super.onResume(); si (mReactInstanceManager != null) { mReactInstanceManager.onResume(este); } } @Anular público void onBackPressed() { si (mReactInstanceManager != null) { mReactInstanceManager.onBackPressed(); } si no { super.onBackPressed(); } } @Anular público booleano onKeyUp(int keyCode, KeyEvent evento) { si (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) { mReactInstanceManager.showDevOptionsDialog(); devolver verdadero; } devolver super.onKeyUp(keyCode, evento); } @Anular público void invokeDefaultOnBackPressed() { super.onBackPressed(); } } |
A few things are happening here:
- You create an Activity that creates a
ReactRootView
, starts a React application inside it and sets it as the main content view. Next, you’re calling theinitCBLite
method which does a few things. - Here you define an empty name and password to be used by the Listener. This means that in theory, anyone could access your database. This is ok for this tutorial but in production you’d replace the line with
new Credentials()
. - Plug in the component to compile the JavaScript Views. We’re not going to use Couchbase Views in this tutorial just yet but it might come in handy.
- Instantiate the
Director
and enable logging. - Start the Couchbase Listener passing in the port to listen on, the manager instance and secure credentials.
That’s all for the Android part, now you can turn your attention to JavaScript!
JavaScript Land
In your project’s root folder, run:
1 2 3 |
$ npm init $ npm instale --guardar reaccionar-nativo $ rizo -o .flowconfig https://raw.githubusercontent.com/facebook/react-native/master/.flowconfig |
This creates a node module for your app and adds the react-native npm dependency. Now open the newly created paquete.json file and add this line inside of the guiones
campo:
1 |
"inicio": "node_modules/react-native/packager/packager.sh" |
Hola Mundo
Copy & paste the following code to a new index.android.js file in your root folder:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
/** * Sample React Native App * https://github.com/facebook/react-native */ 'use strict'; var Reaccione = requiere('react-native'); var Inicio = requiere('./app/components/Home'); var { AppRegistry, StyleSheet, Texto, Ver, ToolbarAndroid } = Reaccione; var estilos = StyleSheet.crear({ contenedor: { flex: 1, backgroundColor: '#111111' }, barra de herramientas: { backgroundColor: '#e9eaed', altura: 56, } }); var TodoLite = Reaccione.createClass({ render: función() { devolver ( ); } }); AppRegistry.registerComponent('TodoLite-ReactNative-Android', () => TodoLite); |
Build and Run!
To run your app, you first need to start the development server. To do this, simply run the following command in your root folder:
1 |
npm iniciar |
NOTA: At the time of this writing, you may need to run brew update && brew reinstall watchman
to update watchman if you get the error Error building DepdendencyGraph: TypeError: Cannot read property 'root' of null
.
Now build and run your Android app in a new Terminal tab:
1 |
./gradlew installDebug |
Open it in the Android simulator and you will see the following:
Well done on getting the development environment up and running! React Native includes great features such live reload which make it much easier to iterate on the UI of the application, but first you must define the models and methods to persist documents to the Couchbase Lite database.
A Todo Application
A Simple API
Crear un nuevo archivo app/utils/api.js y añade lo siguiente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
var api = { // 1 localDatabaseUrl: 'http://localhost:5984', // 2 remoteDatabaseUrl: 'http://localhost:4984', // 3 saveTodo(título){ devolver buscar(este.localDatabaseUrl + '/todos', { método: 'post', cabeceras: { Tipo de contenido: aplicación/json }, cuerpo: JSON.stringify({ tipo: lista, título: título }) }).entonces((res) => res.json()); }, // 4 getTodos(){ devolver buscar(este.localDatabaseUrl + '/todos/_all_docs?include_docs=true').entonces((respuesta) => { si (respuesta.estado !== 200) { devolver buscar(este.localDatabaseUrl + '/todos', { método: 'put', cabeceras: { 'Accept': aplicación/json, Tipo de contenido: aplicación/json }, cuerpo: JSON.stringify({ok: verdadero}) }).entonces((res) => res.json()); } devolver respuesta.json(); }) }, // 5 startSync(){ devolver buscar(este.localDatabaseUrl + '/_replicate', { método: 'post', cabeceras: { Tipo de contenido: aplicación/json }, cuerpo: JSON.stringify({ fuente: 'todos', objetivo: este.remoteDatabaseUrl + '/todos', continuo: verdadero }) }).entonces((res) => res.json()); } }; módulo.exportaciones = api; |
Here is what you’re doing:
- You declare the endpoint the Couchbase Listener is running on.
- The remote database is Sync Gateway in this case. This would be replaced with your Sync Gateway production instance.
- The method to persist a task document.
- Here, you’re getting all the documents from Couchbase Lite.
- Start a push replication from the Couchbase Lite database to Sync Gateway. There could equally be a pull replication as well.
With a basic API in place, you can now turn your attention to building the UI.
Building the UI
Create a new file in app/components/Home.js with the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
var Reaccione = requiere('react-native'); var api = requiere('./../utils/api'); var { Texto, Ver, StyleSheet, ScrollView, TextInput, TouchableOpacity } = Reaccione; var estilos = StyleSheet.crear({ contenedor: { flex: 1 }, buttonText: { fontSize: 18, color: 'white', alignSelf: 'center' }, rowContainer: { acolchado: 10 }, rowTitle: { color: '#48BBEC', fontSize: 16 }, rowContent: { fontSize: 19 }, mainContainer: { flex: 1, acolchado: 30, marginTop: 65, flexDirection: 'column', justifyContent: 'center', backgroundColor: '#48BBEC' }, searchInput: { altura: 50, acolchado: 4, marginRight: 5, fontSize: 23, borderWidth: 1, borderColor: 'white', borderRadius: 8, color: 'white', margen: 5 }, buttonText: { fontSize: 18, color: '#111', alignSelf: 'center' }, botón: { altura: 45, flexDirection: 'row', backgroundColor: 'white', borderColor: 'white', borderWidth: 1, borderRadius: 8, marginBottom: 10, marginTop: 10, alignSelf: 'stretch', justifyContent: 'center' }, }); clase Inicio extiende Reaccione.Componente { constructor(atrezzo) { super(atrezzo); este.estado = { newTodo: '', todos: [] }; } componentWillMount() { api.getTodos() .entonces((res) => { var todos = res.filas.mapa(función (fila) { devolver fila.doc; }); este.setState({ todos: todos }); }); } handleTodoChange(evento) { este.setState({ newTodo: evento.nativeEvent.texto }); } handleSave() { api.saveTodo(este.estado.newTodo) .entonces((res) => { api.getTodos() .entonces((res) => { var todos = res.filas.mapa(función (fila) { devolver fila.doc; }); este.refs.inputText.valor = ''; este.setState({ todos: todos, newTodo: '' }); }); }); } handleSync() { api.startSync() .entonces(función(res) { consola.registro(res); }); } render() { var enumera = este.estado.todos.mapa((artículo, índice) => { devolver ( {artículo.título} ); }); devolver ( Guardar Sincroniza {enumera} ); } } Inicio.propTypes = { enumera: Reaccione.PropTypes.matriz.isRequired }; módulo.exportaciones = Inicio; |
Don’t get intimidated by the length of this code snippet. All we’re doing here is declaring styles and using some built-in React Native UI components to display a text input, buttons and text labels. You can find the list of built-in UI components aquí.
Updating the Root Component
The final step before you can see your great work in action is to update index.android.js to load the Inicio component. Below the requiere
statement to import react-native
, add the following:
1 |
var Inicio = requiere('./app/components/Home'); |
Next, replace the return value of the render
method with . Use the
⌘ + m
shortcut in Genymotion to reload the JavaScript and you should see a bright blue screen. That’s good news!
Replications with Couchbase Sync Gateway
Descargue Sync Gateway desde el siguiente enlace y descomprima el archivo:
http://www.couchbase.com/nosql-databases/downloads
En un nuevo archivo llamado sync-gateway-config.json, paste the following:
1 2 3 4 5 6 7 8 9 |
{ "log": ["*"], "bases de datos": { "todos": { "servidor": "morsa:", "usuarios": { "INVITADO": { "desactivado": falso, "admin_canales": ["*"] } } } } } |
And run Sync Gateway with this config file:
1 |
~/Descargas/couchbase-sincronizar-pasarela/papelera/sync_gateway /ruta/a/proyecto/sincronizar-pasarela-config.json |
To make the Sync Gateway endpoint reachable inside of the Android VM emulator, you need to enable a port from the host to the VM. In Terminal, run the following:
1 |
adb invertir tcp:4984 tcp:4984 |
Open the Admin UI to monitor the documents that were saved to Sync Gateway:
http://localhost:4985/_admin/
Try adding more task documents and notice how they get pushed to Sync Gateway automatically.
Where To Go From Here
Congratulations! You’ve built your first React Native Android + Couchbase Lite application. You’re now ready to add more components such as the following:
- Couchbase Lite Views to write custom queries
- User authentication in a replication
- Continuously deploying the Sync Gateway configuration file and other components
Watch out for a tutorial on debugging your React Native Android + Couchbase Lite application using Charles and Genymotion.
No dudes en compartir tus opiniones, hallazgos o preguntas en los comentarios o en los foros. ¡Hasta pronto!
*Open AndroidManifest.xml located in app/src/main and add the permissions:*
the above line states the permissions but none found in the box given below of it. please add those lines.