Documentation : Files - Android

This example shows how to share binary data between multiple users and devices.

Application consists of only one model, which has one file field. Main screen of application contains list of model entities and two buttons. User can add new entity and synchronize with mobeelizer cloud. There are two users in the system and there's a possibility to switch user on application life time.

Design Your App

To complete this example we need to create new application in Mobeelizer App Designer called Files.

Next we will create model. We have to go to Models section, create new one and call it FileSyncEntity.

Last thing to do to finish this step is creating new field with file type called photo. Note that you shoud use default model credentials (CRUD operations avaliable for everyone). 

For this example we leave conflict resolving setting in overwrite state. 
Next section to configure is 'Groups & Roles', by default there is one group called 'users' and one device category  'mobile' and this two together create role 'users-mobile'. This default configuration is perfect for our example and you don't have to change it. 
When everything is done in Create mode, deploy our application to test environment, and create two users with passwords:

  • a - usera
  • b - userb

Last thing to do in App Designer is download configured template for android with default settings.

Use the Mobeelizer SDK

Extract downloaded zip file then open eclipse and import project.

Add resource files

Before we start  implementing our example we will add some resource files into the project:

Prepare main screen

We will start with creating main screen controls, as you read before on main screen there is list of model entities, two buttons to synchronize and add new entity and enother one to switch between users.

main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
    <RelativeLayout
    	android:layout_width="match_parent"
    	android:layout_height="wrap_content">
    	<TextView
            android:layout_marginTop="20dip"
            android:layout_marginLeft="50dip"
            android:textSize="25dip"
            android:id="@+id/currentUser"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:bufferType="spannable"
            android:text="Files sync" />
    	<ImageButton
            android:layout_height="50dip"
            android:layout_width="50dip"
            android:layout_marginLeft="260dip"
            android:layout_marginTop="10dip"
            android:id="@+id/userSwitch"
            android:layout_alignParentLeft="true"/>
	</RelativeLayout>
        <View
        android:id="@+id/titleBarSeparator"
        android:layout_width="fill_parent"
        android:layout_height="2dip"
        android:layout_alignParentLeft="true"
        android:background="#FFB4B4B4" />
    <ListView
        android:id="@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" >
    </ListView>
	<RelativeLayout
    	android:layout_width="match_parent"
    	android:layout_height="wrap_content"
	    android:layout_marginBottom="10dip">
	    <Button
	        android:layout_height="wrap_content"
	        android:layout_width="100dip"
	        android:layout_marginLeft="10dip"
	        android:layout_marginTop="10dip"
	        android:id="@+id/footerAdd"
	        android:layout_alignParentLeft="true"
	        android:text="Add" />
	    <Button
	        android:layout_height="wrap_content"
	        android:layout_width="100dip"
	        android:layout_marginLeft="10dip"
	        android:layout_marginTop="10dip"
	        android:id="@+id/footerSync"
	        android:layout_toRightOf="@+id/footerAdd"
	        android:text="Sync" />
	</RelativeLayout>
</LinearLayout>

To keep clean in our code we will create an interface to define operations supported by our main screen. First of all we have to have possibility to refresh entities list, there shoud be also posibility to disable and enable all buttons, and to get or set currenty logged in user.

FilePage.java
public interface FilePage {
	void disableButtons();
	
	void enableButtons();
	
	void refreshList();
	
	String getCurrentUser();
	
	void setCurrentUser(String user);
}

When interface is ready let's implement it on FilesActivity. In constructor we will get all created controls in layout definition and store it in class private fields. 

FileActivity.java
public class FilesActivity extends Activity implements FilePage {
	
    private ListView mList;
    
    private TextView mCurrentUser;
    
	private Button mAddButton, mSyncButton;

    private ImageButton mSwitchUserButton;

    private static String currenUser;
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
       
        mAddButton = (Button) findViewById(R.id.footerAdd);
        mSyncButton = (Button) findViewById(R.id.footerSync);
        mSwitchUserButton = (Button) findViewById(R.id.userSwitch);
        mCurrentUser = (TextView) findViewById(R.id.currentUser);
        mList = (ListView) findViewById(android.R.id.list);
        
		// TODO: Add listeners to buttons
    }

	public void disableButtons() {
		mAddButton.setEnabled(false);
		mSyncButton.setEnabled(false);
		mSwitchUserButton.setEnabled(false);
	}

	public void enableButtons() {
		mAddButton.setEnabled(true);
		mSyncButton.setEnabled(true);
		mSwitchUserButton.setEnabled(true);
	}
	public String getCurrentUser() {
		return currenUser;
	}
	public void setCurrentUser(String user) {
		currenUser = user;
        if(user != null){
        	if(currenUser.equals("a")){
        		mSwitchUserButton.setImageResource(R.drawable.bt_user_a_big);
        	}
        	else if(currenUser.equals("b")){
        		mSwitchUserButton.setImageResource(R.drawable.bt_user_b_big);
        	}
        }
	}
	public void refreshList() {
		// TODO: 
	}
}

Switch beetwen users

Before we implement refreshing entities list we have to create switching user things. We will create separated class for that called "SwichUserOperations", this class will implement OnClickListener interface, so object of this class can be set as onClickListener in 'Switch' button. This class will also implement MobeelizerOperationCallback interface and we will use it as a login method callback. 

SwitchUserOperations.java
public class SwitchUserOperations implements OnClickListener , MobeelizerOperationCallback{
	
	private static final String USER_A_LOGIN = "a";
	
	private static final String USER_B_LOGIN = "b";
	
	private static final String USER_A_PASS = "usera";
	
	private static final String USER_B_PASS = "userb";
	private String userToLoggIn;
	private String password;
	
	private FilePage page;
	
	public SwitchUserOperations(FilePage page){
		this.page= page;
	}
	
	public void onClick(View v) {
		page.disableButtons();
		if(page.getCurrentUser() == USER_A_LOGIN){
			userToLoggIn = USER_B_LOGIN;
			password = USER_B_PASS;
		}
		else {
			userToLoggIn = USER_A_LOGIN;
			password = USER_A_PASS;	
		}
		Mobeelizer.login(userToLoggIn, password, this);
	}
	public void onFailure(MobeelizerOperationError arg0) {
		page.setCurrentUser(null);
		page.refreshList();
		page.enableButtons();
	}
	public void onSuccess() {
		page.setCurrentUser(userToLoggIn);
		page.refreshList();
		page.enableButtons();
	}
	
}

It is easy, right? When user click on switch button, we will check which user was logged in recently and then we will login other one. We have to also disable all buttons before login. When login will finish with success we will set current user value in FilePage and then enable all buttons. 

Last thing to do in switching users is setting object of just created class to button as a onclick listener.

FilesActivity.java
	@Override
    public void onCreate(Bundle savedInstanceState) {
        ...
        SwitchUserOperations switchUserOperations = new SwitchUserOperations(this);
        mSwitchUserButton.setOnClickListener(switchUserOperations);
        switchUserOperations.onClick(null);
		...
    }

Get current user entities

Ok we can switch between users now and it is time to create refreshing entity list code. To show mobeelizer files on list we have to create addapter for that, and list item layout template.

Add  photo_list_item.xml file to layout folder, add implement it like this:

photo_list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:minHeight="50dp"
    android:orientation="vertical" >
    <ImageView
        android:id="@+id/listItemImage"
        android:layout_marginLeft="5dp"
        android:layout_marginTop="5dp"
        android:layout_width="40dp"
        android:layout_height="40dp"
         />
    <ImageView
        android:id="@+id/listItemUser"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:layout_marginRight="10dp"/>
</RelativeLayout>

When template is ready it is time to create adapter. Create new class FileListAdapter which extends ArrayAdapter<FileSyncEntity> and override getView method. 

FileListAdapter.java
public class FileListAdapter extends ArrayAdapter<FileSyncEntity> {
	private int resourceId = 0;
	private LayoutInflater inflater;
	public FileListAdapter(Context context, int resource,
			List<FileSyncEntity> objects) {
		super(context, 0, objects);
	    this.resourceId = resource;
	    this.inflater = (LayoutInflater) context
	            .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
	}
	
	public static class ViewHolder {
	    public ImageView author;
	    public ImageView image;
	}
	
	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		View view = convertView;
	    ViewHolder holder;
	    if (view == null) {
	        view = inflater.inflate(resourceId, parent, false);
	        holder = new ViewHolder();
	        holder.author = (ImageView) view.findViewById(R.id.listItemUser);
	        holder.image = (ImageView) view.findViewById(R.id.listItemImage);
	        view.setTag(holder);
	    } else {
	        holder = (ViewHolder) view.getTag();
	    }
	    FileSyncEntity item = (FileSyncEntity) getItem(position);
        MobeelizerFile photo = item.getPhoto();
        // Convert mobeelizer file to Bitmap 
        BitmapFactory.Options opt = new BitmapFactory.Options();
        opt.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(photo.getInputStream(), null, opt);
        opt.inJustDecodeBounds = false;
        opt.inSampleSize = (int) Math.max(Math.round(opt.outHeight / (35.0 )),
                Math.round(opt.outHeight / (35.0 )));
        // Add owner name to author label
        if(item.getOwner().equals("a")){
        	holder.author.setImageResource(R.drawable.bt_user_a_small);
        }
        else if(item.getOwner().equals("b")){
        	holder.author.setImageResource(R.drawable.bt_user_b_small);
        }
	    // Add created bitmap to ImageView
	    holder.image.setImageDrawable(new BitmapDrawable(BitmapFactory.decodeStream(photo.getInputStream(), null, opt)));
	    return view;
	}
}

This code is pretty simple. In constructor we are getting resource of item template definition and list of items. In overridden method we have to convert our FileSyncEntity class object to View object. We are getting TextView and ImageView controls from template and then fill them content with specified values. On the end of this method we are returning View object. 

When adapter is ready it is time to come back to refreshList method in FilesActivity class.

FilesActivity.java
...
	public void refreshList() {
		List<FileSyncEntity> entities = Mobeelizer.getDatabase().list(FileSyncEntity.class);
		FileListAdapter adapter = new FileListAdapter(this, R.layout.photo_list_item, entities);
		mList.setAdapter(adapter);
	}
...

We are getting list of all entities from database, creating new adapter and then setting created object into list control.

Add entity into database

It is time now to implement adding new photo entities to database. As in switch user code we will also creates seperated class for that which will implement OnClickListener. We will get photos from phone camera and we need to implement it in our activity class. New class containg operations to add new entity will use FilePage interface to comunicate with activity so we have to create new async method to support getting photo from activity.

FilePage.java
public interface FilePage {
	...
	void getPhoto(GetPhotoCallback callback);
	
	public interface GetPhotoCallback{
		void onFinished(File file);
	}
}

When FilePage supports getting photo from activity class we can implement it like this:

AddOperation.java
public class AddOperation implements OnClickListener {
	private FilePage view;
	
	public AddOperation(FilePage view){
		this.view = view;
	}
	
	public void onClick(View v) {
		this.view.getPhoto(new GetPhotoCallback() {
			public void onFinished(File file) {
				FileSyncEntity entity = new FileSyncEntity();
				try {
					entity.setPhoto(Mobeelizer.createFile("photo", new BufferedInputStream(new FileInputStream(file))));
					Mobeelizer.getDatabase().save(entity);
				} catch (FileNotFoundException e) {
					e.printStackTrace();
				}
				view.refreshList();
			}
		});
	}
	
}

To be sure that you understand everything. When user click on Add button, onClick method will be invoked, then we are getting photo from FilePage object using async method. In callback of this method we are creating new FileSyncEntity object and adding it to database, on the end we are refreshing list control. 

Ok, let's implement just added method in FilesActivity class.

FilesActivity.java
	@Override
	 public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
		 if(requestCode == 0x100){
			 if(data != null)
			 {
				 Bundle extras = data.getExtras();
				 Bitmap bitmap = (Bitmap)extras.get("data");
				 
				 File photoFile = null;
		            try {
		                // get the photo file from external storage
		                photoFile = new File(getExternalFilesDir(null), "tmp_photo");
		                Log.d("TEST", "Width: " + bitmap.getWidth() + "\tHeight: " + bitmap.getHeight());
		                photoFile.delete();
		                photoFile.createNewFile();
		                if (bitmap.compress(CompressFormat.JPEG, 92, new BufferedOutputStream(new FileOutputStream(photoFile)))) {
		                	bitmap.recycle();
		                }
		                getCallback.onFinished(photoFile);
		            }    
		           catch(Exception e){
		              	e.printStackTrace();
		           }
			 }
		 }
	 }

	 private GetPhotoCallback getCallback;
	
	 public void getPhoto(GetPhotoCallback callback) {
		 getCallback = callback;
		 Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
		 startActivityForResult(intent, 0x100);
	 }	

When getPhoto method is invoked new Intent is created and camera is open. When user takes the photo and accepts it onActivityResult method will be invoked. We are getting taked photo there and invokes callback method. 

Synchronize with mobeelizer

Greate, our example is almost ready, last thing to do is synchronization, but not be afraid it will be as easy as login operation. We will create new class for that again, and we will implements OnClickListener and MobeelizerOperationCallback interfaces in it.

SyncOperations.java
public class SyncOperations implements OnClickListener , MobeelizerOperationCallback {
	
	private FilePage view;
	public SyncOperations(FilePage view){
		this.view = view;
	}
	public void onFailure(MobeelizerOperationError arg0) {
		view.refreshList();
		view.enableButtons();
	}
	public void onSuccess() {
		view.refreshList();
		view.enableButtons();
	}
	public void onClick(View arg0) {
		view.disableButtons();
		if(Mobeelizer.isLoggedIn()){
			Mobeelizer.sync(this);
		}
	}
}

It is actually just calling Mobeelizer sync method. When synchronization finished without any errors given entities list is refreshed, and that is all.

And remember to add created operation classes as buttons onClick listeners.

FilesActivity.java
    @Override
    public void onCreate(Bundle savedInstanceState) {
		...
        mSyncButton.setOnClickListener(new SyncOperations(this));
        mAddButton.setOnClickListener(new AddOperation(this));
    }

Files synchronization is ready, you can test it now on as many devices as you can find. You can also browse your data in App Designer using Data Browser tool. 

Conclusion

To conclude, this is the simple example of using file synchronization with Mobeelizer. Our model consists of only one field. We can create sample records and synchronize with Mobeelizer cloud.

 

Attachments: