Hi, I have been successfully using a combination of Couchbase Server with Couchbase Lite/Sync Gateway for Android for about a year now, with a simple sync function JSON that did not utilize any authentication or channels. I am now at the point where it is important to take advantage of that functionality, and I modified your sample code in order to do this (wonderful tutorial, thank you). I am having no problem pushing data to my server, and according to the HTML interface on localhost:4985, my documents are being routed to the proper channels with the proper accesses granted. However, when I push data from device A, and then attempt to pull data on device B I get nothing in return. My hope is that I am simply making a fundamental error in my Sync Function, so I have provided that as my starting point. If you could take a cursory look and let me know if I am making a stupid mistake I would appreciate it greatly. Otherwise, it’s back to the debugging board. Thanks a lot for your help:
{
  "log": ["*"],
  "databases":{               
    	"test_syncgateway":{
    		"server":"http://10.0.0.84:8091",
    		"bucket":"test_syncgateway",
		"users": {
        		"matt": {"password": "test", "admin_channels": ["matt"]},
        		"bryan": {"password": "test", "admin_channels": ["bryan"]},
        		"mod": {"password": "test", "admin_roles": ["moderator"]},
        		"admin": {"password": "test", "admin_roles": ["admin"]}
      		},
		"roles": {
        		"moderator": {},
        		"admin": {}
      			
    		},
      "sync": 
  	`
      function(doc, oldDoc) {
			/*Validation that document has a type*/
			if (isCreate()) {
    
				// Don't allow creating a document without a type.
    
				validateNotEmpty("documentType", doc.documentType);
  
			} else if (isUpdate()) {
				// Don't allow changing the type of any document.
    
				validateReadOnly("documentType", doc.documentType, oldDoc.documentType);
 
			}
			if (getType() == "numbers") {
    				/* Write access */
				var username = doc._deleted ? oldDoc.username : doc.username
				try {
      
					// Moderators can create or update lists for other users.
      
					requireRole("moderator");
    
				} catch (e) {
      
					// Users can create or update lists for themselves.
      
					requireUser(username);
    
				}
    				/* Validation */
				if (!isDelete()) {
					// Validate required fields.
      
					validateNotEmpty("username", doc.username);
      
					validateNotEmpty("data", doc.data);
					if (isCreate()) {
        
						// Validate that the _id is prefixed by owner.
        
						if (!hasPrefix(doc._id, doc.username + ".")) {
            
							throw({forbidden: "task-list id must be prefixed by list username"});
        
						}
      
					} else {
        
						// Don’t allow task-list ownership to be changed.
        
						validateReadOnly("username", doc.username, oldDoc.username);
      
					}
    
				}
    				/* Routing */
				// Add doc to task-list's channel.
    
				channel("numbers." + doc._id);
    
				channel("moderators");
    				/* Read Access */
				// Grant task-list owner access   
				access(username, "numbers." + doc._id);
				access("role:moderator", "numbers." + doc._id);
 
  			} else {
    
				// Log invalid document type error.
    
				log("Invalid document type: " + doc.documentType);
    
				throw({forbidden: "Invalid document type: " + doc.documentType});
  
			}
 
			
			function getType() {
    
				return (isDelete() ? oldDoc.documentType : doc.documentType);
  
			}
			function isCreate() {
    
				// Checking false for the Admin UI to work
    
				return ((oldDoc == false) || (oldDoc == null || oldDoc._deleted) && !isDelete());
  
			}
	
			function isUpdate() {
    
				return (!isCreate() && !isDelete());
  
			}
   
  
			function isDelete() {
    
				return (doc._deleted == true);
  
			}
  
			function validateNotEmpty(key, value) {
    
				if (!value) {
      
					throw({forbidden: key + " is not provided."});
    
				}
  
			}
  
			function validateReadOnly(name, value, oldValue) {
    
				if (value != oldValue) {
      
					throw({forbidden: name + " is read-only."});
    
				}
  
			}
  
			// Checks whether the provided value starts with the specified prefix
  
			function hasPrefix(value, prefix) {
   
				if (value && prefix) {
   
				return value.substring(0, prefix.length) == prefix
    
				} else {
      
					return false
    
				}
  
			}
    
      }
    `
    }
  }
}