├── .gitignore
├── .gitmodules
├── CONTRIBUTING.md
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── couchbase
│ │ └── todolite
│ │ ├── Application.java
│ │ ├── ImageActivity.java
│ │ ├── ListActivity.java
│ │ ├── LoginActivity.java
│ │ ├── ShareActivity.java
│ │ ├── TaskActivity.java
│ │ ├── UserProfile.java
│ │ └── util
│ │ ├── ImageUtil.java
│ │ ├── LiveQueryAdapter.java
│ │ ├── RoundImageView.java
│ │ └── StringUtil.java
│ └── res
│ ├── drawable-xhdpi
│ ├── ic_camera.png
│ └── ic_camera_light.png
│ ├── layout
│ ├── activity_image.xml
│ ├── activity_list.xml
│ ├── activity_login.xml
│ ├── activity_share.xml
│ ├── activity_task.xml
│ ├── view_dialog_input.xml
│ ├── view_list.xml
│ ├── view_task.xml
│ ├── view_task_create.xml
│ └── view_user.xml
│ ├── menu
│ ├── list.xml
│ ├── list_item.xml
│ ├── task.xml
│ └── task_item.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── values-w820dp
│ └── dimens.xml
│ └── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── sync-gateway-config.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # built application files
2 | *.apk
3 | *.ap_
4 |
5 | # files for the dex VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # generated files
12 | bin/
13 | gen/
14 | build/
15 | out/
16 |
17 | # Local configuration file (sdk path, etc)
18 | local.properties
19 |
20 | # Gradle cache files
21 | .gradle/
22 |
23 | # Android Studio files
24 | *.iml
25 | .idea/
26 | *.ipr
27 | *.iws
28 |
29 | # Eclipse project files
30 | .classpath
31 | .project
32 |
33 | # Proguard folder generated by Eclipse
34 | proguard/
35 |
36 | # Other
37 | .DS_Store
38 | _sandbox
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/couchbaselabs/ToDoLite-Android/eeda0e7fe246baa366a2646e824bc2635fb939ef/.gitmodules
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | We hate bugs, but we love bug reports! And we're grateful to our developers who exercise Couchbase Lite and the Couchbase Sync Gateway in new and unexpected ways and help us work out the kinks.
2 |
3 | We also want to hear about your ideas for new features and improvements. You can file those in the issue trackers too.
4 |
5 | And while we welcome questions, **we prefer to answer questions on our [mailing list](https://groups.google.com/forum/?fromgroups#!forum/mobile-couchbase)** rather than in Github issues.
6 |
7 | # 1. Is This A Duplicate?
8 |
9 | It's great if you can scan the open issues to see if your problem/idea has been reported already. If so, feel free to add any new details or just a note that you hit this too. But if you're in a hurry, go ahead and skip this step -- we'd rather get duplicate reports than miss an issue!
10 |
11 | # 2. Describe The Bug
12 |
13 | ## Version
14 |
15 | Please indicate **what version of the software** you're using. If you compiled it yourself from source, it helps if you give the Git commit ID, or at least the branch name and date ("I last pulled from master on 6/30.")
16 |
17 | If the bug involves replication, also indicate what software and version is on the other end of the line, i.e. "Couchbase Lite Android 1.0" or "Sync Gateway 1.0" or "Sync Gateway commit f3d3229c" or "CouchDB 1.6".
18 |
19 | ## Include Steps To Reproduce
20 |
21 | The most **important information** to provide with a bug report is a clear set of steps to reproduce the problem. Include as much information as possible that you think may be related to the bug. An example would be:
22 |
23 | * Install & run Sync Gateway 1.0.3 on Mac running OS X 10.10.1
24 | * Install app on iPhone 6 running iOS 8.1.1
25 | * Login with Facebook
26 | * Turn off WiFi
27 | * Add a new document
28 | * Turn on WiFi
29 | * Saw no new documents on Sync Gateway (expected: there should have been some documents)
30 |
31 | ## Include Actual vs. Expected
32 |
33 | As mentioned above, the last thing in your steps to reproduce is the "actual vs expected" behavior. The reason this is important is because you may have misunderstood what is supposed to happen. If you give a clear description of what actually happened as well as what you were expecting to happen, it will make the bug a lot easier to figure out.
34 |
35 | ## General Formatting
36 |
37 | Please **format source code or program output (including logs or backtraces) as code**. This makes it easier to read and prevents Github from interpreting any of it as Markdown formatting or bug numbers. To do this, put a line of just three back-quotes ("```") before and after it. (For inline code snippets, just put a single back-quote before and after.)
38 |
39 | **If you need to post a block of code/output that is longer than 1/2 a page, please don't paste it into the bug report** -- it's annoying to scroll past. Instead, create a [gist](https://gist.github.com) (or something similar) and just post a link to it.
40 |
41 | ## Crashes / Exceptions
42 |
43 | If the bug causes a crash or an uncaught exception, include a crash log or backtrace. **Please don't add this as a screenshot of the IDE** if you have any alternative. (In Xcode, use the `bt` command in the debugger console to dump a backtrace that you can copy.)
44 |
45 | If the log/backtrace is long, don't paste it in directly (see the previous section.)
46 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | **This repository is in the process of being deprecated. See the [Android TODO app](https://developer.couchbase.com/documentation/mobile/1.3/training/develop/create-database/index.html) in the training documentation for a more up-to-date sample.
2 |
3 | ## ToDo Lite for Android
4 |
5 | [](https://gitter.im/couchbase/mobile?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
6 |
7 | A shared todo app that shows how to use the [Couchbase Lite Android](https://github.com/couchbase/couchbase-lite-android) framework to embed a nonrelational ("NoSQL") document-oriented database in an Android app and sync it with [Couchbase Server](http://www.couchbase.com/nosql-databases/couchbase-server) in a public or private cloud.
8 |
9 | 
10 |
11 | ## Get the code
12 |
13 | ```
14 | $ git clone https://github.com/couchbaselabs/ToDoLite-Android.git
15 | $ cd ToDoLite-Android
16 | ```
17 |
18 | ## Build and run the app
19 |
20 | * Import the project into Android Studio by selecting `build.gradle` or `settings.gradle` from the root of the project.
21 | * Run the app using the "play" or "debug" button.
22 |
23 | ## Point to your own Sync Gateway
24 |
25 | 1. [Download Sync Gateway](http://www.couchbase.com/nosql-databases/downloads#couchbase-mobile).
26 | 2. Start Sync Gateway with the configuration file in the root of this project.
27 |
28 | ```bash
29 | ~/Downloads/couchbase-sync-gateway/bin/sync_gateway sync-gateway-config.json
30 | ```
31 |
32 | 3. Open **Application.java** and update the `SYNC_URL_HTTP` constant to point to your Sync Gateway instance.
33 |
34 | ```java
35 | private static final String SYNC_URL_HTTP = "http://localhost:4984/todolite";
36 | ```
37 |
38 | You can use the `adb reverse tcp:4984 tcp:4984` command to open the port access from the host to the Android emulator. This command is only available on devices running android 5.0+ (API 21).
39 |
40 | 4. Log in with your Facebook account.
41 | 5. Add lists and tasks and they should be visible on the Sync Gateway Admin UI on [http://localhost:4985/_admin/](http://localhost:4985/_admin/).
42 |
43 | ## Community
44 |
45 | If you have any comments or suggestions, please join [our forum](https://forums.couchbase.com/c/mobile) and let us know.
46 |
47 | ## License
48 |
49 | Released under the Apache license, 2.0.
50 |
51 | Copyright 2011-2014, Couchbase, Inc.
52 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 24
5 | buildToolsVersion "24.0.2"
6 |
7 | defaultConfig {
8 | applicationId "com.couchbase.todolite"
9 | minSdkVersion 16
10 | targetSdkVersion 24
11 | versionCode 130
12 | versionName "1.3.1"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | // workaround for "duplicate files during packaging of APK" issue:
21 | packagingOptions {
22 | exclude 'META-INF/ASL2.0'
23 | exclude 'META-INF/LICENSE'
24 | exclude 'META-INF/NOTICE'
25 | }
26 | }
27 |
28 | dependencies {
29 | compile fileTree(dir: 'libs', include: ['*.jar'])
30 | testCompile 'junit:junit:4.12'
31 | compile 'com.android.support:appcompat-v7:24.2.1'
32 | compile 'com.couchbase.lite:couchbase-lite-android:1.3.1'
33 | // compile 'com.couchbase.lite:couchbase-lite-android-forestdb:1.3.1'
34 | // compile 'com.couchbase.lite:couchbase-lite-android-sqlcipher:1.3.1'
35 | compile 'com.facebook.android:facebook-android-sdk:4.+'
36 | }
37 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/pasin/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
14 |
15 |
16 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
38 |
44 |
45 |
--------------------------------------------------------------------------------
/app/src/main/java/com/couchbase/todolite/Application.java:
--------------------------------------------------------------------------------
1 | package com.couchbase.todolite;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.os.Handler;
7 | import android.widget.Toast;
8 |
9 | import com.couchbase.lite.CouchbaseLiteException;
10 | import com.couchbase.lite.Database;
11 | import com.couchbase.lite.DatabaseOptions;
12 | import com.couchbase.lite.Document;
13 | import com.couchbase.lite.Emitter;
14 | import com.couchbase.lite.Manager;
15 | import com.couchbase.lite.Mapper;
16 | import com.couchbase.lite.View;
17 | import com.couchbase.lite.android.AndroidContext;
18 | import com.couchbase.lite.auth.Authenticator;
19 | import com.couchbase.lite.auth.AuthenticatorFactory;
20 | import com.couchbase.lite.replicator.Replication;
21 | import com.couchbase.lite.util.Log;
22 | import com.couchbase.todolite.util.StringUtil;
23 |
24 | import java.net.MalformedURLException;
25 | import java.net.URL;
26 | import java.util.ArrayList;
27 | import java.util.HashMap;
28 | import java.util.List;
29 | import java.util.Map;
30 |
31 | public class Application extends android.app.Application implements Replication.ChangeListener {
32 | public static final String TAG = "ToDoLite";
33 |
34 | private static final String SYNC_URL_HTTP = "http://us-east.testfest.couchbasemobile.com:4984/todolite";
35 |
36 | // Storage Type: .SQLITE_STORAGE or .FORESTDB_STORAGE
37 | private static final String STORAGE_TYPE = Manager.SQLITE_STORAGE;
38 |
39 | // Encryption (Don't store encryption key in the source code. We are doing it here just as an example):
40 | private static final boolean ENCRYPTION_ENABLED = false;
41 | private static final String ENCRYPTION_KEY = "seekrit";
42 |
43 | // Logging:
44 | private static final boolean LOGGING_ENABLED = true;
45 |
46 | // Guest database:
47 | private static final String GUEST_DATABASE_NAME = "guest";
48 |
49 | private Manager mManager;
50 | private Database mDatabase;
51 | private Replication mPull;
52 | private Replication mPush;
53 | private Throwable mReplError;
54 | private String mCurrentUserId;
55 |
56 | @Override
57 | public void onCreate() {
58 | super.onCreate();
59 | enableLogging();
60 | }
61 |
62 | private void enableLogging() {
63 | if (LOGGING_ENABLED) {
64 | Manager.enableLogging(TAG, Log.VERBOSE);
65 | Manager.enableLogging(Log.TAG, Log.VERBOSE);
66 | Manager.enableLogging(Log.TAG_SYNC_ASYNC_TASK, Log.VERBOSE);
67 | Manager.enableLogging(Log.TAG_SYNC, Log.VERBOSE);
68 | Manager.enableLogging(Log.TAG_QUERY, Log.VERBOSE);
69 | Manager.enableLogging(Log.TAG_VIEW, Log.VERBOSE);
70 | Manager.enableLogging(Log.TAG_DATABASE, Log.VERBOSE);
71 | }
72 | }
73 |
74 | private Manager getManager() {
75 | if (mManager == null) {
76 | try {
77 | AndroidContext context = new AndroidContext(getApplicationContext());
78 | mManager = new Manager(context, Manager.DEFAULT_OPTIONS);
79 | } catch (Exception e) {
80 | Log.e(TAG, "Cannot create Manager object", e);
81 | }
82 | }
83 | return mManager;
84 | }
85 |
86 | public Database getDatabase() {
87 | return mDatabase;
88 | }
89 |
90 | private void setDatabase(Database database) {
91 | this.mDatabase = database;
92 | }
93 |
94 | private Database getUserDatabase(String name) {
95 | try {
96 | String dbName = "db" + StringUtil.MD5(name);
97 | DatabaseOptions options = new DatabaseOptions();
98 | options.setCreate(true);
99 | options.setStorageType(STORAGE_TYPE);
100 | options.setEncryptionKey(ENCRYPTION_ENABLED ? ENCRYPTION_KEY : null);
101 | return getManager().openDatabase(dbName, options);
102 | } catch (CouchbaseLiteException e) {
103 | Log.e(TAG, "Cannot create database for name: " + name, e);
104 | }
105 | return null;
106 | }
107 |
108 | public void loginAsFacebookUser(Activity activity, String token, String userId, String name) {
109 | setCurrentUserId(userId);
110 | setDatabase(getUserDatabase(userId));
111 |
112 | String profileDocID = "p:" + userId;
113 | Document profile = mDatabase.getExistingDocument(profileDocID);
114 | if (profile == null) {
115 | try {
116 | Map properties = new HashMap();
117 | properties.put("type", "profile");
118 | properties.put("user_id", userId);
119 | properties.put("name", name);
120 |
121 | profile = mDatabase.getDocument(profileDocID);
122 | profile.putProperties(properties);
123 |
124 | // Migrate guest data to user:
125 | UserProfile.migrateGuestData(getUserDatabase(GUEST_DATABASE_NAME), profile);
126 | } catch (CouchbaseLiteException e) {
127 | Log.e(TAG, "Cannot create a new user profile", e);
128 | }
129 | }
130 |
131 | startReplication(AuthenticatorFactory.createFacebookAuthenticator(token));
132 | login(activity);
133 | }
134 |
135 | public void loginAsGuest(Activity activity) {
136 | setDatabase(getUserDatabase(GUEST_DATABASE_NAME));
137 | setCurrentUserId(null);
138 | login(activity);
139 | }
140 |
141 | private void login(Activity activity) {
142 | Intent intent = new Intent(activity, ListActivity.class);
143 | intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
144 | activity.startActivity(intent);
145 | activity.finish();
146 | }
147 |
148 | public void logout() {
149 | setCurrentUserId(null);
150 | stopReplication();
151 | setDatabase(null);
152 |
153 | Intent intent = new Intent(getApplicationContext(), LoginActivity.class);
154 | intent.setAction(LoginActivity.ACTION_LOGOUT);
155 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
156 | startActivity(intent);
157 | }
158 |
159 | private void setCurrentUserId(String userId) {
160 | this.mCurrentUserId = userId;
161 | }
162 |
163 | public String getCurrentUserId() {
164 | return this.mCurrentUserId;
165 | }
166 |
167 | /** Replicator */
168 |
169 | private URL getSyncUrl() {
170 | URL url = null;
171 | try {
172 | url = new URL(SYNC_URL_HTTP);
173 | } catch (MalformedURLException e) {
174 | Log.e(TAG, "Invalid sync url", e);
175 | }
176 | return url;
177 | }
178 |
179 | private void startReplication(Authenticator auth) {
180 | if (mPull == null) {
181 | mPull = mDatabase.createPullReplication(getSyncUrl());
182 | mPull.setContinuous(true);
183 | mPull.setAuthenticator(auth);
184 | mPull.addChangeListener(this);
185 | }
186 |
187 | if (mPush == null) {
188 | mPush = mDatabase.createPushReplication(getSyncUrl());
189 | mPush.setContinuous(true);
190 | mPush.setAuthenticator(auth);
191 | mPush.addChangeListener(this);
192 | }
193 |
194 | mPull.stop();
195 | mPull.start();
196 |
197 | mPush.stop();
198 | mPush.start();
199 | }
200 |
201 | private void stopReplication() {
202 | if (mPull != null) {
203 | mPull.removeChangeListener(this);
204 | mPull.stop();
205 | mPull = null;
206 | }
207 |
208 | if (mPush != null) {
209 | mPush.removeChangeListener(this);
210 | mPush.stop();
211 | mPush = null;
212 | }
213 | }
214 |
215 | @Override
216 | public void changed(Replication.ChangeEvent event) {
217 | Throwable error = null;
218 | if (mPull != null) {
219 | if (error == null)
220 | error = mPull.getLastError();
221 | }
222 |
223 | if (error == null || error == mReplError)
224 | error = mPush.getLastError();
225 |
226 | if (error != mReplError) {
227 | mReplError = error;
228 | if (mReplError != null)
229 | showErrorMessage(mReplError.getMessage(), null);
230 | }
231 | }
232 |
233 | /** Database View */
234 | public View getListsView() {
235 | View view = mDatabase.getView("lists");
236 | if (view.getMap() == null) {
237 | Mapper mapper = new Mapper() {
238 | public void map(Map document, Emitter emitter) {
239 | String type = (String)document.get("type");
240 | if ("list".equals(type))
241 | emitter.emit(document.get("title"), null);
242 | }
243 | };
244 | view.setMap(mapper, "1.0");
245 | }
246 | return view;
247 | }
248 |
249 | public View getTasksView() {
250 | View view = mDatabase.getView("tasks");
251 | if (view.getMap() == null) {
252 | Mapper map = new Mapper() {
253 | @Override
254 | public void map(Map document, Emitter emitter) {
255 | if ("task".equals(document.get("type"))) {
256 | List