Pages

Gradle: Working with multi app projects in Android Studio

In this tutorial I wanted to share how is my project organized to work with multiple apps and libraries.




I have the following settings.gradle in the root of my Project:

include ':app'
include ':appBookShelf'
include ':appNFC'
include ':appUnitConverter'
include ':appUserPreferences'
include ':libraries:ListViewSwipeActivity'



Please note that I perpend my app names with "app.." to keep them organized, I also have a folder "libraries" where I keep the code of my submodules.

Here is my folder structure:


CIT299 $ t
.
├── CIT299.iml
├── app
│   ├── app.iml
│   ├── build
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src
├── appBookShelf
│   ├── build
│   ├── build.gradle
│   └── src
├── appNFC
│   ├── build
│   ├── build.gradle
│   └── src
├── appUnitConverter
│   ├── appUnitConverter.iml
│   ├── build
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src
├── appUserPreferences
│   ├── build.gradle
│   └── src
├── build.gradle
├── gradle
│   └── wrapper
├── gradle.properties
├── gradlew
├── gradlew.bat
├── libraries
│   └── ListViewSwipeActivity
├── local.properties
└── settings.gradle



Maven: updating version on Mac

In this tutorial we will discuss upgrading Maven on Mac OS X.




While trying building with Maven I was getting errors related to version number (below 3.1) 

mvn install
Error: Could not find or load main class org.codehaus.plexus.classworlds.launcher.Launcher

To update my maven I have to do the following sequence of steps.

$ mvn -version

Apache Maven 3.0.5 (r01de14724cdef164cd33c7c8c2fe155faf9602da; 2013-02-19 07:51:28-0600)


I had to uninstall the older version 3.0

$ brew uninstall maven30

Uninstalling /usr/local/Cellar/maven30/3.0.5...


Trying to install newest I noticed that I tried it before, but it was not linked

$ brew install maven

Warning: maven-3.2.3 already installed, it's just not linked


The easiest way to fix that is to uninstall and install again:

$ brew uninstall maven

Uninstalling /usr/local/Cellar/maven/3.2.3...
$ brew install maven

==> Downloading http://www.apache.org/dyn/closer.cgi?path=maven/maven-3/3.2.3/binaries/apache-maven-3.2.3-bin.tar.gzAlready downloaded: /Library/Caches/Homebrew/maven-3.2.3.tar.gz
🍺  /usr/local/Cellar/maven/3.2.3: 76 files, 8.0M, built in 3 seconds
uki@ CNH_PROD $ mvn -version
Apache Maven 3.2.3



Maven: installing Android SDK

In order to compile Android project using Maven you have to instal Android SDK to your Maven repository.


$ git clone https://github.com/mosabua/maven-android-sdk-deployer.git
Cloning into 'maven-android-sdk-deployer'...
remote: Counting objects: 3198, done.
remote: Compressing objects: 100% (7/7), done.
remote: Total 3198 (delta 3), reused 6 (delta 3)
Receiving objects: 100% (3198/3198), 435.92 KiB | 0 bytes/s, done.
Resolving deltas: 100% (1585/1585), done.
Checking connectivity... done.

$ cd maven-android-sdk-deployer/


Android NFC

In this tutorial we will create a simple NFC app as a new project from scratch.

Step: Create and new Project "NFC" (not Module)



Set minimum version to Eclair API v7



Select FragmentActivity (most common you will use)





Name the Activity "MainActivity"



inspect settings.gradle

include ':app'


inspect local.properties (specific to locatin of your Android SDK directory)

sdk.dir=/Users/uki/Documents/Dropbox/Android/android-sdk-macosx


inspect gradle.properties

- no configuration

inspect build.gradle

note that gradle plugin is newer that in project created last week

classpath 'com.android.tools.build:gradle:0.12.2'


inspect NFC/app/build.gradle

notice the newest versions of tools API 21 - Lollipop


apply plugin: 'com.android.application'
android {
    compileSdkVersion 21
    buildToolsVersion "21.0.2"
    defaultConfig {
        applicationId "com.chicagoandroid.nfc"
        minSdkVersion 7
        targetSdkVersion 21
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            runProguard false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:21.0.0'
}

Step: run the app


everything should work without any changes.


Step: open NFC/app/src/main/AndroidManifest.xml


  • Add permission within <application> tag to allow use of NFC
  • Add feature if you want your app to be available only to phones with NFC


        <uses-permission android:name="android.permission.NFC" />
        <uses-feature
            android:name="android.hardware.nfc"
            android:required="true" />
    </application>


Alternatively to uses-feature you will be able to check if device supports NFC in runtime getDefaultAdapter() is null.

Step: change minSdkVersion

  • change to API 9 if you want basic NFC support
  • change to API 10 is you want full support
  • change to API 14 to take advantage of newest API and Android Beam (device-to-device)

Previously we would set AndroidManifest.xml 
        <uses-sdk android:minSdkVersion="14"/>
You can test run the app, even using version 21 and it will not have an effect.

Since we build with build.gradle we will change it there:

apply plugin: 'com.android.application'
android {
    compileSdkVersion 21
    buildToolsVersion "21.0.2"
    defaultConfig {
        applicationId "com.chicagoandroid.nfc"
        minSdkVersion 14
...

Step: add NFC intent filter

               <action android:name="android.nfc.action.NDEF_DISCOVERED" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="text/plain" />
            </intent-filter>

Run project and try to scan a tag.  The app does not pick it up.

Step: tech-list.xml resource

  • add new directory src/main/res/xml
  • add new file tech_list.xml


<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
    <tech-list>
        <tech>android.nfc.tech.IsoDep</tech>
        <tech>android.nfc.tech.NfcA</tech>
        <tech>android.nfc.tech.NfcB</tech>
        <tech>android.nfc.tech.NfcF</tech>
        <tech>android.nfc.tech.NfcV</tech>
        <tech>android.nfc.tech.Ndef</tech>
        <tech>android.nfc.tech.NdefFormatable</tech>
        <tech>android.nfc.tech.MifareClassic</tech>
        <tech>android.nfc.tech.MifareUltralight</tech>
    </tech-list>
</resources>


Run project and try to scan a tag. 





Android: reinstalling several APKs from single folder

In this tutorial we will install multiple APKs from your local computer to Android device using ADB connection.

echo "                    ██╗"
echo "██╗   ██╗ ██╗   ██╗ ╚═╝"
echo "██║   ██║ ██╚═██╗═╝ ██╗"
echo "██║   ██║ ████══╝   ██║"
echo "██║   ██║ ██║╚██╗   ██║"
echo "████████║ ██║  ╚██╗ ██║"
echo "╚═══════╝  ╚╝   ╚═╝ ╚═╝"
echo

# Install new APKs
for apk in $(ls *.apk)
do
    echo "Installing $apk"
    echo
    adb install -r "$apk"
done



ChildrenTV story

ChildrenTV is a mobile app that provides an impressive collection of new and old classic cartoons that you can filter by language and age category.

“The original idea came to me while I was inspired by seeing my daughter search and watch her favorite YouTube cartoons.  So we decided to create ChildrenTV mobile app, a cute and useful application that makes the process of teaching foreign languages an interactive session for both parents and kids."- said Uki D. Lucas, ChildrenTV founder.


Over the past 3 years, his mobile development company CyberWalkAbout.com has worked to develop the ChildrenTV  application and has continuously updated its content. Praised for its successful combination of beautiful graphics and educational content, the title gained a huge popularity on the Android an iOS markets and received positive feedback. The app integrates with the existing web site YouTube and streams live hundreds of safe videos that can be easily filtered by language and age category.




With more than 1,000 videos and growing, the app provides a fun way to keep kids both occupied and educated during long car rides as well as babysit them at home.  Cute and colorful graphics, an array of entertaining cartoons in 10 languages, together with a kid-friendly interface make ChildrenTV a great addition to a children’s mobile library. Also, as a parent you can brush up on a foreign language skills using this app, too!
 
We have lot of users from different countries, speaking MANY LANGUAGES, since it is almost impossible to find favorite videos in so many languages we ask you to help us, your and other kids will be thankful. Please suggest videos that your kids like, and we will do our best to make them available in the Children TV apps  http://bit.ly/ChildrenTV_YouTube_form



Lever Action Rifle Calibers

Recently, I have been thinking about a new lever-action rifle. The appeal of these late 1800 models is that they are great, very light (5lbs) for backpacking and pack enough punch for anything you need in the woods/bush. Lever action rifles, comparing to heavy (~8.5lbs) bolt action calibers (.30-06, 7.62x54R) we normally shoot are far more maneuverable in the thicket and honestly safer with the heavy animals like a boar, elk or moose because of their  10+1 capacity and rapid fire ability.



I have been thinking about the caliber I would like to pick up, here are the options I considered. Please be aware that I reload my ammo and I am not planning to pay $2 per round of Buffalo Bore. Also, I don't like revolvers so there is no appeal for me of carrying a matching hand piece, I like my 1911 .45 ACP just fine for "personal" defense.


  • .38 Special / .357 Magnum - while it would be a great caliber for fun plinking and smaller game and home-defense I decided that I need to rule it out because it is definitely not a boar caliber, an important criterium for me.
  • .44-40 Winchester - this is the grandpa of all lever-action cartridges and while it is plenty powerful and honestly cool, it is not recommended as a boar load.
  • .44 Special / .44 Magnum - this combination is easy to buy (or reload) with many options available (305gr.@1,325fps/1,189ft-lbs.). The .44 Special is a great round for plinking and self defense, .44 magnum is designed for hunting and it is a good boar load (deer and everything else smaller).  I have a feeling that this combination is the best for lever action rifle
  • .45 Colt - honestly I wanted this one the most, especially that I already reload .45 ACP which share similar tooling. The .45 Colt while most of the time weak, can have very powerful +P options from DoubleTap (335 gr.,1605 fps, 1916 ft. lbs. in 16 inch barrel).
  • .454 Casull caliber that does all that .44 and .45 Colt can, but on average has much more power (360gr.@1,425fps/1,623ft-lbs.), the cartridge is still SHORT enough in length so you can carry many of them. What is most important you can shoot .45 Colt out of .454 Casull chambering rifle, all you have to do is clean the chamber after you do to wipe the carbon deposits before you use .454 round again. I would have to make some extra-light loads (250 gr. & TrailBoss@1,100fps) for it for plinking and I would have the LIGHT, MEDIUM and HEAVLY rifle in one package. This way I can shoot regular load .45 Colt for 80% of my needs and have .454 for boar hunting and for the Yukon/Alaska trip I dream about.
  • .45-70 Government - It is definitely plenty of gun (430gr.@1,925fps/3,537ft-lbs.) for ALL dangerous animals in the back country (boar, bear, moose, bison, mountain lion). The problem with this round is that it is HUGE and takes the valuable space in the magazine tube of the rifle
  • .30-30 - This is a great and proven lever action rifle cartridge, but similarly to .45-70 I feel that it is too long and takes too much space. 






Rifle Models I like in order of my preference:



  • .454 Casul R92 Carbine Lever Action Rifle, 16" Round Barrel, LOA 34", 4.8 lbs, 8+1 tubular magazine, $742.20, 0 available, #R92-68016
  • .454 Casull Rossi R92 Blue Lever Action Rifle, 20" Round Barrel, LOA 37.5", 10+1 tubular magazine, $665 - 0 available #R92-68001 
  • .45 Colt R92 Large Loop Lever Action Rifle, 4.8 lbs, 16" Round Barrel, Blue, LOA 34", Saddle Ring, 8+1 tubular magazine, $635.08, 7 available, #R92-57006
  • .45 Colt, 16" Round Barrel, 8+1 tubular magazine,  #R92-57008 $624 - 15 available
  • .45 Magnum R92 Carbine Lever Action Rifle, 16" Round Barrel, 8+1 tubular magazine, LOA 34", 4.8 lbs,,  $624 - 22 available l #R92-55008

































Groovy on Android

Android developers are already using Groovy in many places AROUND Android, for example in Gradle build scripts. Now the dream of using Groovy as primary language for Android is a little closer to realization! See what is cooking.


http://melix.github.io/blog/2014/06/grooid.html

also a good interview with Cédric Champeau, Senior Software Engineer working on Groovy for SpringSource/Pivotal here:

http://www.infoq.com/news/2014/06/groovy-android


Installing Maven

After few years of not needing it, I came back to Maven, reason being that I cannot use Gradle for reasons beyond my control and Ant does not describe complex projects structures required.


Getting new version of Maven


$ brew install maven
==> Downloading ....
/usr/local/Cellar/maven/3.2.3:
$ mvn -version

Apache Maven 3.2.3
$ which mvn
/usr/local/bin/mvn


Setting M2_HOME


$ echo $M2_HOME
# nada


I added the following in my ~/.profile




# Maven - updated: Oct 20, 2014
export MAVEN_HOME=/usr/local/Cellar/maven/3.2.3/
export PATH=${PATH}:${MAVEN_HOME}/bin
export M2_HOME=/usr/local/Cellar/maven/3.2.3/
export PATH=${PATH}:${M2_HOME}/libexec


and run:

$ echo $M2_HOME
/usr/local/Cellar/maven/3.2.3/



Bash: GIT repetitive tasks

I have too many GIT repos to remember that I need to keep updated, to do that I have a script that updates what I need.




function git_do() {
echo
if [ -z "$1" ]
then
echo 'Please provide a directory name e.g. do my_directory_here'
exit 0
fi
cd $1
pwd
git config --get remote.origin.url
git fetch
git checkout develop
git rebase
git status
}
clear
git_do repo1
git_do repo2/module/repo3
git_do repo2/module/repo4
etc.



Mac Daemons that hunt us

Sometimes you get annoyed by garbage other companies are trying to stuff on your computer. Motorola is one of these companies. When I connect my test DROID device the annoying popups are opened. 

This is not a conclusive solution, but a start of the longer article on the unwanted Mac Daemons that suck up your patience and CPU power.

Removing Launch services


$ launchctl list | grep moto
- 0 com.motorola.motohelperUpdater
32594 - com.motorola.motohelper
- 0 com.motorola.MDMUpdaterPlist
$ launchctl remove com.motorola.motohelper

$ launchctl remove com.motorola.motohelperUpdater


Removing unwanted Application



cd /Library/Application\ Support/
$ ls | grep Moto
MotoCast

Motorola Mobility
uki@ Application Support $ sudo rm -r MotoCast
Password:

uki@ Application Support $ sudo rm -r Motorola\ Mobility/



List of legitimate Services:


  • PTPCamera - part of the Image Capture software that MacOS uses


Got Droid? - My Android (test) devices

While cleaning my office I decided to make an inventory of my Android devices.

Please note that it is NOT worth testing on anything less than API 8 Android 2.2 Froyo as it is less than 0.7% of the market share,  so it is OK to use support library v7 in all apps you are running. 
I keep my Google ION for "sentimental reasons only.


OSOS versionVersion NameAPI LEVEL% usedMakeModelCPU nameCPU MhzCPU numberflash ROMRAMCamera MpBTRadioscreen heightscreen widthscreen density dpiScreen diagonal inches
Android1.6Donut40HTCGoogle ION / Magic528132quad band unlocked3.2
Android2.2.3Froyo80.7MotorolaDROIDArm Cortex A860015128544802403.7
Android/CM2.3.3Gingerbread MR11011.4BNNookColor10246001617
Android2.3.3Gingerbread MR11011.4HTCVision / Desire Z A7272ARMv7 Scorpion80011.5Gb51252.18004802403.7
Android2.3.4Gingerbread MR11011.4MotorolaDROID2 A955OMAP 3630100018Gb51252.18544803.7
Android4.2.2Jelly Bean1720.7SamsungGT-P7510 Tab 10.1128075216010.1
Android4.1.11720.7HTCOne X11967203204.9
Android4.4.2KitKat1924.5SamsungSM-P990 Galaxy Tab 12.12560160032012.1
Android/CM4.4.4KitKat1924.5OnePlusOne A0001Snapdragon 8014192010804805.5













Android name to version to API level table


  • API 1 (no code name) 1.0  
  • API 1 (no code name) 1.1  
  • API 3 Cupcake 1.5, NDK 1
  • API 4 Donut 1.6, NDK 2
  • API 5 Eclair 2.0  
  • API 6 Eclair 2.0.1  
  • API 7 Eclair 2.1, NDK 3 - support-v7 ActionBar
  • API 8 Froyo 2.2.x  , NDK 4
  • API 9 Gingerbread 2.3 - 2.3.2  , NDK 5 - added some NFC
  • API 10 Gingerbread 2.3.3 - 2.3.7   - added full NFC
  • API 11 Honeycomb 3.0  
  • API 12 Honeycomb 3.1  , NDK 6
  • API 13 Honeycomb 3.2.x  
  • API 14 Ice Cream Sandwich 4.0.1 - 4.0.2, NDK 7 - added NFC: Android Beam
  • API 15 Ice Cream Sandwich 4.0.3 - 4.0.4 , NDK 8
  • API 16 Jelly Bean 4.1.x  
  • API 17 Jelly Bean 4.2.x  
  • API 18 Jelly Bean 4.3.x  
  • API 19 KitKat 4.4 - 4.4.4  
  • API 20 Android Wear devices
  • API 21 Lollipop 5.0 API level 21


My devices spreadsheet





8b. ListViewSwipeActivity

In this tutorial you will learn how to use ListViewSwipeActivity.



/**
 * This class extends https://github.com/UkiDLucas/ListViewSwipeActivity
 */
public class MainActivity extends ListViewSwipeActivity {



   @Override
   public ListView getListView() {
      return booksListView;
   }



   /**
    * Implement this method to handle detected swipe events.
    */
   @Override
   public void onSwipeRightToLeft(int position) {
      Toast.makeText(this, "Detected  right-to-left swipe.", Toast.LENGTH_LONG).show();
      Log.i(TAG, "Detected  right-to-left swipe." + position);
      Book book = bookDatabase.fetch(bookIndexCards.get(position));
      Log.w(TAG, "Deleting book " + book.toString());
      bookDatabase.delete(book);
      displayAllDataBaseBooks();
   }




   /**
    * Implement this method to handle detected swipe events.
    */
   @Override
   public void onSwipeLeftToRight(int itemSwiped) {
      Toast.makeText(this, "Detected  left-to-right swipe.", Toast.LENGTH_LONG).show();
   }



@Override
public void onItemClickListener(ListAdapter adapter, int position) {
Toast.makeText(this, "Detected a touch/tap on item in position " + position, Toast.LENGTH_LONG).show();



Book book = bookDatabase.fetch(position);
Log.i(TAG, "Deleting book " + position);
bookDatabase.delete(book);
}



6a. Java: Generic Type Interface

In this tutorial we will learn how to create an Interface that serves any type of object using Java Generic Types introduced in Java 1.5.


Step 1: Create interface


Note you can put it in any package, but this Interface is NOT your project specific, it is not even Android specific.


package com.cyberwalkabout.database;
import java.util.List;
/**
 * Created by uki on 10/11/14.
 * This interface simply assures that we don't forget to implement most important methods.
 * We are using Generic TYPE T as we don't know what objects we will be using in the database.
 * The TYPE T could stand for any object e.g. Book, Person, Address, etc.
 * You could add more methods of your own, or better method parameters.
 */
public interface DatabaseCrud<T> {
   /**
    * Saves an object to the database.
    */
   public void create(T object);
   /**
    * This methods reads one record by id.
    * Please notice it returns Generic Type T.
    */
   public T read(int dbRecordId);
   /**
    * fetches all objects that match the String searchText
    */
   public List<T> fetch(String searchText);
   /**
    * Update given object in the database.
    */
   public int update(T object);
   /**
    * Deletes given object from the database.
    */
   public void delete(T object);
}

Step 2: Implement the Interface in your specific database wrapper code

public class BookSqlHelper extends SQLiteOpenHelper implements DatabaseCrud<Book> {



Make sure you auto-copy JavaDocs from the Interface:



Step 3: Implement your generated methods



The IDE automatically generates method stubs like this:

    /**
     * Deletes given object from the database.
     *
     * @param object
     */
    @Override
    public void delete(Book object) {
     
    }



Now, you only have to fill in the blanks, note I changed name of the object from object to book:

    /**
     * Inserts a Book object to the database.
     * Please note that the Interface uses Generic Type T:
     * public void create(T object);
     */
    @Override
    public void create(Book book) {
        Log.w(TAG + "save()", book.toString());
        SQLiteDatabase db = this.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put(FIELD__TITLE, book.getTitle());
        values.put(FIELD__AUTHOR, book.getAuthor());
        values.put(FIELD__ISBN, book.getIsbn());
        values.put(FIELD__LOCATION, book.getLocation());
        db.insert(TABLE_BOOKS, null, values);
        db.close();
    }




9b. Gradle: AndroidStudio with submodules (library projects)

In this tutorial we will overview integration basics of Android Studio and Gradle build tools.



Step 1: Verify local.properties file

## This file is automatically generated by Android Studio.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file should *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
sdk.dir=/Users/uki/Documents/Dropbox/Android/android-sdk-macosx


As noted, this is for us to only verify that we have the right Android tools directory specified.

I also have $ANDROID_HOME  in my ~/.profile which serves the same purpose

# ANDROID - updated: Oct 17, 2014
export ANDROID_HOME=~/Documents/Dropbox/Android/android-sdk-macosx
export PATH=${PATH}:${ANDROID_HOME}/build-tools/21.0.0
export PATH=${PATH}:${ANDROID_HOME}/platform-tools
export PATH=${PATH}:${ANDROID_HOME}/tools

Remember to update build-tools to latest stable version, in my case Android Lollipop 5.0. API 21

If you are developing code with multiple people, verify that .gitignore file contains files that are specific only to you.

local.properties

Step 2: Gradle Project structure with library projects







Step 3: Gradle Wrapper "gradlew" permission to execute


I had to add execute (+x) permissions to my gradlew script

$ ./gradlew
-bash: ./gradlew: Permission denied
chmod +x gradlew


Run ./gradlew (.bat)

$ ./gradlew
Relying on packaging to define the extension of the main artifact has been deprecated and is scheduled to be removed in Gradle 2.0
:help
Welcome to Gradle 1.10.
To run a build, run gradlew <task> ...
To see a list of available tasks, run gradlew tasks
To see a list of command-line options, run gradlew --help

If you inspect gradle/wrapper/gradle-wrapper.properties you will notice it uses older Gradle jar:


$ more gradle/wrapper/gradle-wrapper.properties#Fri Oct 17 10:58:51 CDT 2014
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=http\://services.gradle.org/distributions/gradle-1.10-all.zip




However updating it to a newer one did not produce correct results, so I left it at original 1.10.

Step: Preventing duplicate dependencies

UNEXPECTED TOP-LEVEL EXCEPTION:
com.android.dex.DexException: Multiple dex files define Landroid/support/v7/appcompat/R$anim;

Make sure that if your Library project has:

dependencies {
    compile 'com.android.support:appcompat-v7:19.+'
...

then you main App should not have the different version of the same jar.

Also do:

Build > Clean Project

Step: before writing of  a new build script always update Android SDK



Step: Writing example app build.gradle

While writing Gradle script use Android Studio tips:



The sections listed below are in the same order as in my project/app/build.grade file:

Step: plugin Android or Java?


apply plugin: 'com.android.application'



Step: buildscript{}



// build script is not necessary unless you want to provide non-standard configuration
buildscript {
repositories {
//mavenCentral()
// even if not necessary, I am giving example of other repo in case failure of mavenCentral
//jcenter()
}
dependencies {
// This is Android plugin for Gradle, not actual Gradle 2.1
// classpath 'com.android.tools.build:gradle:0.12.+'
}
}

Step: dependencies{}


dependencies {
    /**
     *  Include all jars from the lib directory.
     */
    compile fileTree(dir: 'libs', include: ['*.jar'])
    /**
     * You must install or update the Support Repository through the SDK Manager to use this dependency.
     * v4 is needed for Theme.AppCompat.Light.DarkActionBar
     **/
    compile 'com.android.support:support-v4:21.0.0'
    /**
     * Even if NOT recommended I use 19.+ notation, also I am not using 21.+ because of errors:
     * No resource found that matches the given name 'android:TextAppearance.Material'.
     * even if my SDK Manager is up to date with API 21.
     * since support-v7 is included in the libraries project it is not needed here.
     */
    // compile 'com.android.support:appcompat-v7:21.0.0'
    compile 'com.android.support:appcompat-v7:19.+'
    /**
     * The main app depends on LIBRARY ListViewSwipeActivity
     */
    compile project(':libraries:ListViewSwipeActivity')
}

Step: repositories{}



/**
 * needed only if you have to specify non-standard configuration
 **/
repositories {
    mavenCentral()
    /**
     * even if not necessary, I am giving example of other repo in case failure of mavenCentral
     */
    //jcenter()
}


android{}

android {
    /**
     * compileSdkVersion is the same as the target property in the project.properties
     */
    compileSdkVersion 21 // Lollipop 5.0
    /**
     * Check Android SDK Manager for the NEWEST tools version:
     */
    buildToolsVersion '21.0.2' // Lollipop 5.0
    defaultConfig {
        versionCode 1
        versionName "1.0"
        /**
         * minSdkVersion should be set to 7 if you depend on support-v7.
         */
        minSdkVersion 7
        targetSdkVersion 21  // Lollipop 5.0
    }
    buildTypes {
        release {
            runProguard false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }
    }
    /**
     * I am using default Gradle locations,
     * but if you use Ant based projects you may have to adjust
     */
    sourceSets {
        main {
            manifest.srcFile 'src/main/AndroidManifest.xml'
            java.srcDirs = ['src/main/java']
            aidl.srcDirs = ['src/main/aidl']
            res.srcDirs = ['src/main/res']
            assets.srcDirs = ['src/main/assets']
            //renderscript.srcDirs = ['src']
            //resources.srcDirs = ['src']
        }
        /**
         * androidTest.setRoot moves the whole sourceSet (and its sub folders) to a new folder.
         * This moves src/androidTest
         */
        androidTest.setRoot('tests')
    }
}




Android: ListViewSwipeActivity ListView with swipe detection

In this tutorial we will explore writing and using ListView that extends support-v7 ActionBarActivity and detects side-to-side swipes and touch events.

download the source code from:

git clone https://github.com/UkiDLucas/ListViewSwipeActivity/


Or review code:

ListViewSwipeActivity.java


8a. Android: populating ListView from the Databse

In this tutorial we will expand on the previous example showing basic SQLite CRUD methods. We will populate a list with results from the database.


Step 1: Create content for your database. 

We will use a separate object that provides the books, this way we keep our activity clean.

package com.chicagoandroid.cit299.week7.bookshelf;

import com.chicagoandroid.cit299.week7.bookshelf.model.Book;

import java.util.ArrayList;
import java.util.List;

/**
* Created by uki on 10/16/14.
* This class creates fake list of books,
* however it could import them from an XML file
* or fetch them from the Internet API.
*/
public class MockBookBuilder {

/**
* This is a static method that returns a list of mock books.
*/
public static List<Book> createBooks() {
Book book;
List<Book> books = new ArrayList<Book>();

// Create a new "empty" book with no properties.
book = new Book();

book.setAuthor("Reto Meier");
book.setTitle("Professional Android 4 Application Development");
book.setIsbn("1118102274");
book.setLocation("");
books.add(book);

// Create a new "empty" book with no properties.
book = new Book();

book.setAuthor("Paul Deitel, at al.");
book.setTitle("Android for Programmers An App-Driven Approach");
book.setIsbn("0-13-357092-4");
book.setLocation("");
books.add(book);

return books;
}
}

Step 2: adjust Activity onCreate() to populate ListView


import com.cyberwalkabout.activity.v7.actionbar.listview.ListViewSwipeActivity;

/**
* This class extends https://github.com/UkiDLucas/ListViewSwipeActivity
*/
public class MainActivity extends ListViewSwipeActivity {
private final static String TAG = MainActivity.class.getSimpleName();

/**
* UI element that displays books found.
*/
ListView booksListView;
/**
* bookIndexCards will help us keep track where is each book in the ListView.
*/
Map<Integer, Integer> bookIndexCards = new HashMap<Integer, Integer>();

/**
* Wrapper around Book database operations.
*/
BookSqlHelper bookDatabase;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
booksListView = (ListView) findViewById(R.id.booksListView);

bookDatabase = new BookSqlHelper(this);
addNewBooksToDatabase();
}

@Override
protected void onResume() {
super.onResume();
displayAllDataBaseBooks();
}

Step: 


   private void addNewBooksToDatabase() {
      String tag = TAG + ".addNewBooksToDatabase()";
      List<Book> fetchedBooks;
      // We save all books, one-by-one to database.
      for (Book book : MockBookBuilder.createBooks()) {
         // Comparing Books by only Title is not the best idea, it may lead to unexpected results.
         fetchedBooks = bookDatabase.fetch(book.getTitle());
         if (fetchedBooks.size() > 0) {
            Log.i(tag, "Found this book in the database, not adding!");
         }
         else {
            Log.i(tag, "New book, adding: " + book.toString());
            bookDatabase.save(book);
         }
      }
   }


Step: 

   private void displayAllDataBaseBooks() {
      List<String> resultList = new ArrayList<String>();
      List<Book> databaseBooks = bookDatabase.fetchAll();
      bookIndexCards.clear();
      int index = 0;
      for (Book book : databaseBooks) {
         bookIndexCards.put(index, book.getId());
         resultList.add(book.toString());
         index++;
      }
      final ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, resultList);
      booksListView.setAdapter(adapter);
   }



7b. SQLite CRUD - BookShelf app

In this tutorial you will learn basic SQLite CRUD  functions (Create, Read, Update, Delete) for database operations. Actually, we will use Save, Fetch, Update and Delete method names.

Step 1: Create a new IntelliJ/Android Studio Project




  • project name: Week7
  • module name: BookShelf





Step 2: The first version of this app will not have any UI, edit strings.xml


<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">Book Shelf - SQLite example</string>
    <string name="hello_world">See logcat output!</string>
    <string name="action_settings">Settings</string>
</resources>



Step 3: Run the app to make sure everything is OK so far


Step 4: Create new package "model" and new Java class "Book"


In this example we will be operating on the OBJECT Book, therefore we need a model for it.

package com.chicagoandroid.cit299.week7.bookshelf.model;
public class Book {
      private int id;
      private String title;
      private String author;
      private String isbn;
      private String location;
      /**
       * Constructor with no parameters
       */
      public Book() {
      }
      /**
       * Constructor with title and author parameters
       * @param title
       * @param author
       */
      public Book(String title, String author) {
            super();
            this.title = title;
            this.author = author;
      }
      /**
       * Constructor with ISBN parameter
       * @param isbn
       */
      public Book(String isbn) {
            super();
            this.isbn = isbn;
      }


For our convenience we will override toString() method that will show us the content of the Book. 

      @Override
      public String toString() {
            return "Book: id=" + id
                  + "\n title = " + title
                  + "\n author = " + author
                  + "\n isbn = " + isbn;
      }

Step 5: Generate getters and Setters methods for Book.java





Step 6: Create new package "database" and new Java Interface "DatabaseCrud"



package com.cyberwalkabout.database;

import java.util.List;

/**
* Created by uki on 10/11/14.
* This interface simply assures that we don't forget to implement most important methods.
* We are using Generic TYPE T as we don't know what objects we will be using in the database.
* The TYPE T could stand for any object e.g. Book, Person, Address, etc.
* You could add more methods of your own, or better method parameters.
*/
public interface DatabaseCrud<T> {

/**
* Saves an object to the database.
*/
public void create(T object);

/**
* This methods reads one record by id.
* This record has to be in the Database to have id.
* Please notice it returns Generic Type T.
*/
public T read(int dbRecordId);

/**
* fetches all objects that match the String searchText
*/
public List<T> fetch(String searchText);

/**
* Update given object in the database.
*/
public int update(T object);

/**
* Deletes given object from the database.
* This method should wrap delete(int objectDbId);
*/
public void delete(T object);
}


Step 7: Create Java class "BookSqlHelper"



package com.chicagoandroid.cit299.week7.bookshelf.database;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;

import com.chicagoandroid.cit299.week7.bookshelf.model.Book;
import com.cyberwalkabout.database.DatabaseCrud;

import java.nio.Buffer;
import java.util.LinkedList;
import java.util.List;

public class BookSqlHelper extends SQLiteOpenHelper implements DatabaseCrud<Book> {
private static final String TAG = BookSqlHelper.class.getSimpleName();

private static final int DATABASE_VERSION = 1;
private static final String DATABASE_NAME = "DB_BOOK_SHELF";
private static final String TABLE_BOOKS = "books";
private static final String FIELD_ID = "id";
private static final String FIELD__TITLE = "title";
private static final String FIELD__AUTHOR = "author";
private static final String FIELD__ISBN = "isbn";
private static final String FIELD__LOCATION = "location";

private static final String[] COLUMNS = { //
FIELD_ID, // 0
FIELD__TITLE, // 1
FIELD__AUTHOR, // 2
FIELD__ISBN, // 3
FIELD__LOCATION // 4
};

public BookSqlHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}

@Override
public void onCreate(SQLiteDatabase db) {
String CREATE_BOOK_TABLE = //
"CREATE TABLE " + TABLE_BOOKS + " ( " //
+ FIELD_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " //
+ FIELD__TITLE + " TEXT, " //
+ FIELD__AUTHOR + " TEXT, " //
+ FIELD__ISBN + " TEXT, " //
+ FIELD__LOCATION + " TEXT " //
+ ")";
db.execSQL(CREATE_BOOK_TABLE);
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS books");
this.onCreate(db);
}

Step 8 : Add "create" method of BookSqlHelper.java

    /**
     * Inserts a Book object to the database.
     */
    @Override
    public void create(Book book) {
        Log.w(TAG + "save()", book.toString());
        SQLiteDatabase db = this.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put(FIELD__TITLE, book.getTitle());
        values.put(FIELD__AUTHOR, book.getAuthor());
        values.put(FIELD__ISBN, book.getIsbn());
        values.put(FIELD__LOCATION, book.getLocation());
        db.insert(TABLE_BOOKS, null, values);
        db.close();
    }

Step 9: Implement "read" method of BookSqlHelper.java


    /**
     * This methods reads one record by id.
     *
     * @param dbBookId
     */
    @Override
    public Book read(int dbBookId) {
        SQLiteDatabase db = this.getReadableDatabase();
        Cursor cursor = db.query( //
                TABLE_BOOKS, // String table
                COLUMNS, // String[] columns
                " id = ?", // selection
                new String[]{String.valueOf(dbBookId)}, // String[] selection arguments
                null, // String group by
                null, // String having
                null, // String order by
                null); // String limit
        return getBooksFromCursor(cursor).get(0);
    }



Step 9 : Implement "update" method of BookSqlHelper.java

/**
* Update given Book object in the database.
*/
@Override
public int update(Book book) {

SQLiteDatabase db = this.getWritableDatabase();

ContentValues values = new ContentValues();
values.put("title", book.getTitle());
values.put("author", book.getAuthor());
values.put("isbn", book.getIsbn());
values.put("location", book.getLocation());

int i = db.update( //
TABLE_BOOKS, // String table
values, // ContentValues values - column/value pairs
FIELD_ID + " = ?", // String where clause
new String[]{String.valueOf(book.getId()) // String[] where arguments
});
db.close();
Log.w(TAG + "update(Book book)", book.toString());
return i;
}



Step 10: Implement "delete" method(s)


    /**
     * Deletes given object from the database.
     */
    @Override
    public void delete(Book book) {
        delete(book.getId());
        Log.d(TAG + "delete", book.toString());
    }
    /**
     * Delete database object by it's id.
     *
     * @param bookDbId - database id of the object to be deleted.
     */
    public void delete(int bookDbId) {
        SQLiteDatabase db = this.getWritableDatabase();
        db.delete( //
                TABLE_BOOKS, // String table
                FIELD_ID + " = ?", // String where clause
                new String[]{String.valueOf(bookDbId) // String[] where arguments
                });
        db.close();
        Log.d(TAG + "delete(int bookDbId)", "ID: " + bookDbId);
    }