Create a simple File Chooser in Android
1. Android File Chooser?
In the Windows operating system, you are very familiar with File Chooser. It helps you to choose the files that are available in your computer. A question in your mind is whether or not there is something similar in Android?
Unfortunately, File Chooser is not available in Android. Butdon't worry, in this article, I am going to guide you to create a FileChooserFragment that simulates a File Chooser, that you can reuse it in your projects.
2. Example of Android File Chooser
No ADS
Example preview:
Note: You need a few files to test this example on the Android Emulator. OK, on the Device File Explorer window copy some files to the "sdcard/Download" folder, like the following illustration:
First, create a new project on Android Studio:
- File > New > New Project > Empty Activity
- Name: FileChooserExample
- Package name: org.o7planning.filechooserexample
- Language: Java
Second, add the following code to AndroidManifest.xml in order to tell Android that, "Please permit this application to get access to the external storage of the device."
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.o7planning.filechooserexample">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Note: Android 6.0+ (API Level 23+) requires a higher level of security, so your application needs to ask the user for permission to be accessible to the external storage of the device (See how to handle in the code example).
FileChooserFragment
Next, create a FileChooserFragment, then you can use this Fragment in many other places in your project.
Right click on the project, then select:
- File > New > Fragment > Fragment (Blank)
After that, open fragment_file_chooser.xml to design FileChooserFragment.
Then set ID and Text for the components in the interface:
fragment_file_chooser.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:dividerPadding="5sp"
tools:context=".FileChooserFragment">
<EditText
android:id="@+id/editText_path"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ems="10"
android:inputType="textUri" />
<Button
android:id="@+id/button_browse"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:text="Browse" />
</LinearLayout>
FileChooserFragment.java
package org.o7planning.filechooserexample;
import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class FileChooserFragment extends Fragment {
private static final int MY_REQUEST_CODE_PERMISSION = 1000;
private static final int MY_RESULT_CODE_FILECHOOSER = 2000;
private Button buttonBrowse;
private EditText editTextPath;
private static final String LOG_TAG = "AndroidExample";
@Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_file_chooser, container, false);
this.editTextPath = (EditText) rootView.findViewById(R.id.editText_path);
this.buttonBrowse = (Button) rootView.findViewById(R.id.button_browse);
this.buttonBrowse.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
askPermissionAndBrowseFile();
}
});
return rootView;
}
private void askPermissionAndBrowseFile() {
// With Android Level >= 23, you have to ask the user
// for permission to access External Storage.
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { // Level 23
// Check if we have Call permission
int permisson = ActivityCompat.checkSelfPermission(this.getContext(),
Manifest.permission.READ_EXTERNAL_STORAGE);
if (permisson != PackageManager.PERMISSION_GRANTED) {
// If don't have permission so prompt the user.
this.requestPermissions(
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
MY_REQUEST_CODE_PERMISSION
);
return;
}
}
this.doBrowseFile();
}
private void doBrowseFile() {
Intent chooseFileIntent = new Intent(Intent.ACTION_GET_CONTENT);
chooseFileIntent.setType("*/*");
// Only return URIs that can be opened with ContentResolver
chooseFileIntent.addCategory(Intent.CATEGORY_OPENABLE);
chooseFileIntent = Intent.createChooser(chooseFileIntent, "Choose a file");
startActivityForResult(chooseFileIntent, MY_RESULT_CODE_FILECHOOSER);
}
// When you have the request results
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
//
switch (requestCode) {
case MY_REQUEST_CODE_PERMISSION: {
// Note: If request is cancelled, the result arrays are empty.
// Permissions granted (CALL_PHONE).
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.i( LOG_TAG,"Permission granted!");
Toast.makeText(this.getContext(), "Permission granted!", Toast.LENGTH_SHORT).show();
this.doBrowseFile();
}
// Cancelled or denied.
else {
Log.i(LOG_TAG,"Permission denied!");
Toast.makeText(this.getContext(), "Permission denied!", Toast.LENGTH_SHORT).show();
}
break;
}
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case MY_RESULT_CODE_FILECHOOSER:
if (resultCode == Activity.RESULT_OK ) {
if(data != null) {
Uri fileUri = data.getData();
Log.i(LOG_TAG, "Uri: " + fileUri);
String filePath = null;
try {
filePath = FileUtils.getPath(this.getContext(),fileUri);
} catch (Exception e) {
Log.e(LOG_TAG,"Error: " + e);
Toast.makeText(this.getContext(), "Error: " + e, Toast.LENGTH_SHORT).show();
}
this.editTextPath.setText(filePath);
}
}
break;
}
super.onActivityResult(requestCode, resultCode, data);
}
public String getPath() {
return this.editTextPath.getText().toString();
}
}
Once you have the FileChooserFragment, you can use it anywhere in the project.
Set ID and Text for the components in the interface:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="5sp"
android:paddingTop="5sp"
android:paddingRight="5sp"
android:paddingBottom="5sp"
tools:context=".MainActivity" >
<fragment
android:id="@+id/fragment_fileChooser"
android:name="org.o7planning.filechooserexample.FileChooserFragment"
android:layout_width="0dp"
android:layout_height="45dp"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button_showInfo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Show Info"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/fragment_fileChooser" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.java
package org.o7planning.filechooserexample;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.FragmentManager;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private FileChooserFragment fragment;
private Button buttonShowInfo;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager fragmentManager = this.getSupportFragmentManager();
this.fragment = (FileChooserFragment) fragmentManager.findFragmentById(R.id.fragment_fileChooser);
this.buttonShowInfo = this.findViewById(R.id.button_showInfo);
this.buttonShowInfo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showInfo();
}
});
}
private void showInfo() {
String path = this.fragment.getPath();
Toast.makeText(this, "Path: " + path, Toast.LENGTH_LONG).show();
}
}
FileUtils.java
package org.o7planning.filechooserexample;
import android.annotation.SuppressLint;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.provider.OpenableColumns;
import android.text.TextUtils;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
public class FileUtils {
private static final String LOG_TAG = "FileUtils";
private static Uri contentUri = null;
/**
* Get a file path from a Uri. This will get the the path for Storage Access
* Framework Documents, as well as the _data field for the MediaStore and
* other file-based ContentProviders.<br>
* <br>
* Callers should check whether the path is local before assuming it
* represents a local file.
*
* @param context The context.
* @param uri The Uri to query.
*/
@SuppressLint("NewApi")
public static String getPath(final Context context, final Uri uri) {
// Check here to KITKAT or new version
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
String selection = null;
String[] selectionArgs = null;
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
String fullPath = getPathFromExtSD(split);
if (fullPath != "") {
return fullPath;
} else {
return null;
}
}
// DownloadsProvider
else if (isDownloadsDocument(uri)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
final String id;
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(uri, new String[]{MediaStore.MediaColumns.DISPLAY_NAME}, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
String fileName = cursor.getString(0);
String path = Environment.getExternalStorageDirectory().toString() + "/Download/" + fileName;
if (!TextUtils.isEmpty(path)) {
return path;
}
}
} finally {
if (cursor != null) {
cursor.close();
}
}
id = DocumentsContract.getDocumentId(uri);
if (!TextUtils.isEmpty(id)) {
if (id.startsWith("raw:")) {
return id.replaceFirst("raw:", "");
}
String[] contentUriPrefixesToTry = new String[] {
"content://downloads/public_downloads",
"content://downloads/my_downloads"
};
for (String contentUriPrefix : contentUriPrefixesToTry) {
try {
final Uri contentUri = ContentUris.withAppendedId(Uri.parse(contentUriPrefix), Long.valueOf(id));
// final Uri contentUri = ContentUris.withAppendedId(
// Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
return getDataColumn(context, contentUri, null, null);
} catch (NumberFormatException e) {
// In Android 8 and Android P the id is not a number
return uri.getPath().replaceFirst("^/document/raw:", "").replaceFirst("^raw:", "");
}
}
}
} else {
final String id = DocumentsContract.getDocumentId(uri);
final boolean isOreo = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
if (id.startsWith("raw:")) {
return id.replaceFirst("raw:", "");
}
try {
contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
} catch (NumberFormatException e) {
e.printStackTrace();
}
if (contentUri != null) {
return getDataColumn(context, contentUri, null, null);
}
}
}
// MediaProvider
else if (isMediaDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
Uri contentUri = null;
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
selection = "_id=?";
selectionArgs = new String[]{split[1]};
return getDataColumn(context, contentUri, selection,
selectionArgs);
} else if (isGoogleDriveUri(uri)) {
return getDriveFilePath(uri, context);
}
}
// MediaStore (and general)
else if ("content".equalsIgnoreCase(uri.getScheme())) {
if (isGooglePhotosUri(uri)) {
return uri.getLastPathSegment();
}
if (isGoogleDriveUri(uri)) {
return getDriveFilePath(uri, context);
}
if( Build.VERSION.SDK_INT == Build.VERSION_CODES.N) {
// return getFilePathFromURI(context,uri);
return getMediaFilePathForN(uri, context);
// return getRealPathFromURI(context,uri);
} else {
return getDataColumn(context, uri, null, null);
}
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
return null;
}
/**
* Check if a file exists on device
*
* @param filePath The absolute file path
*/
private static boolean fileExists(String filePath) {
File file = new File(filePath);
return file.exists();
}
/**
* Get full file path from external storage
*
* @param pathData The storage type and the relative path
*/
private static String getPathFromExtSD(String[] pathData) {
final String type = pathData[0];
final String relativePath = "/" + pathData[1];
String fullPath = "";
// on my Sony devices (4.4.4 & 5.1.1), `type` is a dynamic string
// something like "71F8-2C0A", some kind of unique id per storage
// don't know any API that can get the root path of that storage based on its id.
//
// so no "primary" type, but let the check here for other devices
if ("primary".equalsIgnoreCase(type)) {
fullPath = Environment.getExternalStorageDirectory() + relativePath;
if (fileExists(fullPath)) {
return fullPath;
}
}
// Environment.isExternalStorageRemovable() is `true` for external and internal storage
// so we cannot relay on it.
//
// instead, for each possible path, check if file exists
// we'll start with secondary storage as this could be our (physically) removable sd card
fullPath = System.getenv("SECONDARY_STORAGE") + relativePath;
if (fileExists(fullPath)) {
return fullPath;
}
fullPath = System.getenv("EXTERNAL_STORAGE") + relativePath;
if (fileExists(fullPath)) {
return fullPath;
}
return fullPath;
}
private static String getDriveFilePath(Uri uri, Context context) {
Uri returnUri = uri;
ContentResolver contentResolver = context.getContentResolver();
Cursor returnCursor = contentResolver.query(returnUri, null, null, null, null);
// Get the column indexes of the data in the Cursor,
// move to the first row in the Cursor, get the data,
// and display it.
int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE);
returnCursor.moveToFirst();
String name = (returnCursor.getString(nameIndex));
String size = (Long.toString(returnCursor.getLong(sizeIndex)));
File file = new File(context.getCacheDir(), name);
try {
InputStream inputStream = context.getContentResolver().openInputStream(uri);
FileOutputStream outputStream = new FileOutputStream(file);
int read = 0;
int maxBufferSize = 1 * 1024 * 1024;
int bytesAvailable = inputStream.available();
// int bufferSize = 1024;
int bufferSize = Math.min(bytesAvailable, maxBufferSize);
final byte[] buffers = new byte[bufferSize];
while ((read = inputStream.read(buffers)) != -1) {
outputStream.write(buffers, 0, read);
}
Log.e("File Size", "Size " + file.length());
inputStream.close();
outputStream.close();
Log.e("File Path", "Path " + file.getPath());
Log.e("File Size", "Size " + file.length());
} catch (Exception e) {
Log.e(LOG_TAG, e.getMessage());
}
return file.getPath();
}
private static String getMediaFilePathForN(Uri uri, Context context) {
Uri returnUri = uri;
Cursor returnCursor = context.getContentResolver().query(returnUri, null, null, null, null);
// Get the column indexes of the data in the Cursor,
// move to the first row in the Cursor, get the data,
// and display it.
int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE);
returnCursor.moveToFirst();
String name = (returnCursor.getString(nameIndex));
String size = (Long.toString(returnCursor.getLong(sizeIndex)));
File file = new File(context.getFilesDir(), name);
try {
InputStream inputStream = context.getContentResolver().openInputStream(uri);
FileOutputStream outputStream = new FileOutputStream(file);
int read = 0;
int maxBufferSize = 1 * 1024 * 1024;
int bytesAvailable = inputStream.available();
//int bufferSize = 1024;
int bufferSize = Math.min(bytesAvailable, maxBufferSize);
final byte[] buffers = new byte[bufferSize];
while ((read = inputStream.read(buffers)) != -1) {
outputStream.write(buffers, 0, read);
}
Log.e("File Size", "Size " + file.length());
inputStream.close();
outputStream.close();
Log.e("File Path", "Path " + file.getPath());
Log.e("File Size", "Size " + file.length());
} catch (Exception e) {
Log.e(LOG_TAG, e.getMessage());
}
return file.getPath();
}
private static String getDataColumn(Context context, Uri uri,
String selection, String[] selectionArgs) {
Cursor cursor = null;
final String column = "_data";
final String[] projection = {column};
try {
cursor = context.getContentResolver().query(uri, projection,
selection, selectionArgs, null);
if (cursor != null && cursor.moveToFirst()) {
final int index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(index);
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return null;
}
/**
* @param uri - The Uri to check.
* @return - Whether the Uri authority is ExternalStorageProvider.
*/
private static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
/**
* @param uri - The Uri to check.
* @return - Whether the Uri authority is DownloadsProvider.
*/
private static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
/**
* @param uri - The Uri to check.
* @return - Whether the Uri authority is MediaProvider.
*/
private static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
/**
* @param uri - The Uri to check.
* @return - Whether the Uri authority is Google Photos.
*/
private static boolean isGooglePhotosUri(Uri uri) {
return "com.google.android.apps.photos.content".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is Google Drive.
*/
private static boolean isGoogleDriveUri(Uri uri) {
return "com.google.android.apps.docs.storage".equals(uri.getAuthority()) //
|| "com.google.android.apps.docs.storage.legacy".equals(uri.getAuthority());
}
}
No ADS
Android Programming Tutorials
- Format Credit Card Number with Android TextWatcher
- Android Networking Tutorial with Examples
- Android TextInputLayout Tutorial with Examples
- Create a simple File Finder Dialog in Android
- Android AutoCompleteTextView and MultiAutoCompleteTextView Tutorial with Examples
- Using image assets and icon assets of Android Studio
- Android AsyncTaskLoader Tutorial with Examples
- Android ImageSwitcher Tutorial with Examples
- Android TimePickerDialog Tutorial with Examples
- Android FrameLayout Tutorial with Examples
- Android TimePicker Tutorial with Examples
- How to disable the permissions already granted to the Android application?
- Android ContextMenu Tutorial with Examples
- Android Camera Tutorial with Examples
- Get Phone Number in Android using TelephonyManager
- Android Clipboard Tutorial with Examples
- Android DatePickerDialog Tutorial with Examples
- Android SMS Tutorial with Examples
- Android Space Tutorial with Examples
- Android ScrollView and HorizontalScrollView Tutorial with Examples
- Install Android Studio on Windows
- Android VideoView Tutorial with Examples
- Android ProgressBar Tutorial with Examples
- Android External Storage Tutorial with Examples
- Android 2D Game Tutorial for Beginners
- Enable USB Debugging on Android Device
- Android MediaPlayer Tutorial with Examples
- Android Phone Call Tutorial with Examples
- Example of an explicit Android Intent, calling another Intent
- Android TextWatcher Tutorial with Examples
- Configure Android Emulator in Android Studio
- Android PopupMenu Tutorial with Examples
- Android CharacterPickerDialog Tutorial with Examples
- Android ListView with Checkbox using ArrayAdapter
- Android RadioGroup and RadioButton Tutorial with Examples
- Android AsyncTask Tutorial with Examples
- Android SnackBar Tutorial with Examples
- Android Spinner Tutorial with Examples
- Android ListView Tutorial with Examples
- Android TextView Tutorial with Examples
- Android SeekBar Tutorial with Examples
- Android JSON Parser Tutorial with Examples
- How to know the phone number of Android Emulator and change it
- Android LinearLayout Tutorial with Examples
- Android ViewPager2 Tutorial with Examples
- Android Text to Speech Tutorial with Examples
- Android RatingBar Tutorial with Examples
- Android DialogFragment Tutorial with Examples
- Android Wifi Scanning Tutorial with Examples
- Android Fragments Tutorial with Examples
- Create a simple File Chooser in Android
- Android DatePicker Tutorial with Examples
- Example of implicit Android Intent, open a URL, send an email
- Using Android Device File Explorer
- Android ToggleButton Tutorial with Examples
- Google Maps Android API Tutorial with Examples
- How to add external libraries to Android Project in Android Studio?
- Android EditText Tutorial with Examples
- Android ImageButton Tutorial with Examples
- Android Notifications Tutorial with Examples
- Android CheckBox Tutorial with Examples
- Android Services Tutorial with Examples
- Android Dialog Tutorial with Examples
- Android Tutorial for Beginners - Basic examples
- Create a custom Android Toast
- Android Intents Tutorial with Examples
- Android WebView Tutorial with Examples
- Android Chip and ChipGroup Tutorial with Examples
- Android GridView Tutorial with Examples
- Android Switch Tutorial with Examples
- How to remove applications from Android Emulator?
- Android QuickContactBadge Tutorial with Examples
- Android Chronometer Tutorial with Examples
- Android OptionMenu Tutorial with Examples
- Install Intel® HAXM for Android Studio
- Playing Sound effects in Android with SoundPool
- Android Internal Storage Tutorial with Examples
- Android AlertDialog Tutorial with Examples
- Android SharedPreferences Tutorial with Examples
- ChipGroup and Chip Entry Example
- Android SQLite Database Tutorial with Examples
- Android Toast Tutorial with Examples
- Android TableLayout Tutorial with Examples
- Android Button Tutorial with Examples
- Android FloatingActionButton Tutorial with Examples
- What is needed to get started with Android?
- Android UI Layouts Tutorial with Examples
- Android CardView Tutorial with Examples
- Setting SD Card for Android Emulator
- Android ImageView Tutorial with Examples
- Android Tutorial for Beginners - Hello Android
- Android StackView Tutorial with Examples
- Android TextClock Tutorial with Examples
Show More