Android Notes



Using Custom Adapter to save and render fetched data (image) in AsyncTask

    private class FetchMovieListTask extends AsyncTask<String, Void, ArrayList<MovieData>> {
 
        private ArrayList<MovieData> getMovieList(String jsonString) {
            while (json) {
                MovieData movieData = new MovieData(...);
                movieList.add(movieData);
            }
            return movieList;
        }
        protected ArrayList<MovieData> doInBackground(String... params) {
            return getMovieList(responseStr);
        }
        protected void onPostExecute(ArrayList<MovieData> movieList) {
            for (MovieData movieData : movieList) {
                mAdapter.add(movieData);
            }
        }
    }
 
    private class ImageAdapter extends ArrayAdapter<MovieData> {
        public View getView(int position, View convertView, ViewGroup parent) {
            ImageView imageView;
            if (convertView == null) {
                imageView = new ImageView(getContext());
                : initialize some attributes
            } else {
                imageView = (ImageView) convertView;
            }
            MovieData movieData = getItem(position);
            // Takes care of downloading & showing it in sync with UI thread, as well as caching images:
            Picasso.with(getContext())
                    .load(url)
                    .placeholder(R.raw.hourglass_image)
                    .into(imageView);
            return imageView;
        }
 


Picasso Image Library


http://www.101apps.co.za/articles/gridview-tutorial-using-the-picasso-library.html

 
    mAdapter = new ImageAdapter(getActivity(), R.layout.movie_item_image, R.id.movie_item_imageview, initialList);
    // ??? This layout/view doesn't seem to make much difference as even TextView also shows gridview images!! May be that's because of below overridden getView()???
    :
    private class ImageAdapter extends ArrayAdapter<String> {
        public ImageAdapter(Context context, int resource, int textViewResourceId, ArrayList<String> objects) {
            super(context, resource, textViewResourceId, objects);
        }
 
        public View getView(int position, View convertView, ViewGroup parent) {
 
            // takes care of downloading & showing it in sync with UI thread, as well as caching images.
            Picasso.with(getContext())
                    .load(url)
                    .placeholder(R.raw.image_placeholder)
                    .into(imageView);
            return imageView;
        }



Obtaining Unique PendingIntents for Associated Intents

A PendingIntent itself is simply a reference to a token maintained by the system describing the original data used to retrieve it. If you use two Intent objects that are equivalent, then the same PendingIntent is provided for both of them.

For purposes of intent resolution (filtering)/retrieving a PendingIntent, two Intents are considered to be equivalent if their action, data, type, class, and categories are the same. This DOES NOT compare any extra data included in the intents!! See Intent.filterEquals.

So even if extras differ, to obtain Unique PendingIntents (per instance of the target purpose/activity/service/broadcast), either forcefully set unique Data in Intent, or use unique Request code while creating the PendingIntent.

        final Bundle extras = new Bundle();
        extras.putInt(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
        final Intent settingsActivityIntent = new Intent(context, YmdhmsAppWidgetSettings.class)
                .putExtras(extras)
                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // in case activity is started outside of the context of an existing activity
        // IMPORTANT: PendingIntent will be reused even if extras differ between the intents targeted for different instances of AppWidgets.
        // This creates a problem by using latest extras (appWidgetId) for ALL previous and new instances of AppWidgets!!
        // One option is to make Intent's Data unique by using toUri, effectively forcing creation of separate PendingIntents:
        settingsActivityIntent.setData(Uri.parse(settingsActivityIntent.toUri(Intent.URI_INTENT_SCHEME)));
        // Or, Another better/cleaner solution is to unique requestCode for creating unique PendingIntents for each AppWidget.
        final PendingIntent pendingIntent = PendingIntent.getActivity(context, appWidgetId, // use unique request code to create unique PendingIntents
                settingsActivityIntent, PendingIntent.FLAG_UPDATE_CURRENT);



 

Dynamically Adding Views (based on data/options)

  • Create an empty "place-holder anchor" LinearLayout movie_review_list
  • Dynamically inflate movie_review_link views
  • Instantiate and add into the list
 
        ArrayList<String[]> mReviews;
        list = (LinearLayout) rootView.findViewById(R.id.movie_review_list);
        for ( int i=0; i < movieReviews.size(); i++ ) {
            View vi = inflater.inflate(R.layout.movie_review_link, null);
            ((TextView)vi.findViewById(R.id.movie_review_link_textview))
                    .setText(movieReviews.get(i)[0]);
            list.addView(vi);
        }
 


 

setTag() & getTag() on Button/Text View (similar to PutExtra for intents)

Attach custom data using setTag() to a Button/Text view for later retrieval onClick: (similar to PutExtra for intents)
 
            Button readButton = (Button) vi.findViewById(R.id.movie_review_read_button);
            readButton.setTag(movieReviews.get(i)[1]); // Save/attach review uri, which would be used onClick.
            readButton.setOnClickListener(
              new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    String uriString = (String) v.getTag();
                    Log.v(LOG_TAG, "Review url: " + uriString);
                }
 


 

Invoking Fragment's methods from Activity

public class MainActivity extends ActionBarActivity {
        // First tag a fragment during committing transaction:
        getSupportFragmentManager().beginTransaction()
                .add(R.id.container, new ForecastFragment(), FORECASTFRAGMENT_TAG)
                .commit();

        // When needed, locate the fragment and invoke its method:
        ForecastFragment ff = (ForecastFragment)getSupportFragmentManager().findFragmentByTag(FORECASTFRAGMENT_TAG);
        ff.onLocationChanged();
    }
 


 

Adding JUnit support in a project

Add Sunshine\app\src\androidTest\java\com\example\shreekant\sunshine\app\FullTestSuite.java
 
FullTestSuite contains all Java test classes packaged into a suite that JUnit can run.
Thus, additional tests can be added by just adding a java class file.
 
    public class TestPractice extends AndroidTestCase {

        protected void setUp() throws Exception { super.setUp(); }          // called before each test
        protected void tearDown() throws Exception { super.tearDown(); }    // called after each test

        // A method with prefix "test" is automatically called by Test Manager:
        public void testThatDemonstratesAssertions() throws Throwable {
            // should have at least one assert
            assertEquals("X should be equal", a, c);
        }
 
To run tests,
Android Studio => Sunshine => app => src => androidTest => java => com.example.shreekant.sunshine.app
=> Right click -> Run Tests in 'com.example...
      Select the one with Android icon (and not the Gradle icon)




Formatting Strings

    <xliff:g> marks message parts that should not be translated.
 
    <!-- Date format for displaying day of week and date (i.e. Mon Jun 1) [CHAR LIMIT=20] -->
    <string name="format_full_friendly_date"><xliff:g id="day">%1$s</xliff:g>, <xliff:g id="date">%2$s</xliff:g></string>
 
    <!-- Format for displaying Temperature and Unicode U+00B0 ° degree sign [CHAR LIMIT=20] -->
    <string name="format_temperature"><xliff:g id="temperature">%1.0f</xliff:g>\u00B0</string>
 
    return String.format(context.getString(
            R.string.format_full_friendly_date,
            today,
            date));
    Or, a shorter version:
    return context.getString(R.string.format_full_friendly_date,
            today,
            date);
 
    return context.getString(R.string.format_temperature, temp);




List Item Selection Color

values/colors.xml
    <color name="grey">#cccccc</color>
    <color name="sunshine_light_blue">#ff64c2f4</color>
 
    <color name="red">#ff0000</color>
    <color name="green">#7eff00</color>
    <color name="cyan">#00ffff</color>
    <color name="magenta">#7e00ff</color>
 
 
drawable/touch_selector.xml: ## Create empty selector so that reference to "@drawable/touch_selector" doesn't crash on devices prior to Honeycomb API v11.
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
        <!-- Here it's a stub for API < v11.  Appropriate method of indicating touch states is defined for
        higher API versions -->
    <selector>
 
drawable-v11/touch_selector.xml: ## Background attributes/state_activated are NOT SUPPORTED prior to Honeycomb API v11.
    <item android:state_pressed="true"
      android:drawable="@color/grey" />
    <!-- When the view is "activated".  In SINGLE_CHOICE_MODE, it flags the active row
         of a ListView -->
    <item android:state_activated="true"
          android:drawable="@color/sunshine_light_blue" />
 
drawable-v21/touch_selector.xml: ## Use new "ripple" UI for v21 and above.
    <item android:state_pressed="true">
      <ripple android:color="@color/grey" />
    </item>
    <!-- When the view is "activated".  In SINGLE_CHOICE_MODE, it flags the active row
         of a ListView -->
    <item android:state_activated="true"
          android:drawable="@color/sunshine_light_blue" />
 
values/styles.xml
    <style name="ForecastListStyle">
        <!-- Here it's a stub.  In screens of sufficient width, this style includes modifications
        for two-pane layout -->
    </style>
 
values-sw600dp/styles.xml: ## Setting choiceMode to singleChoice enables appropriate type of "activated" touch_selector based on API version.
    <style name="ForecastListStyle">
        <item name="android:choiceMode">singleChoice</item>
    </style>

 
List & List Item Layout: ## Finally, use style to distinguish between phone & tablet behavior

 
Apply API version based appropriate type of background touch selector to represent states of a list item:

    <LinearLayout
        android:background="@drawable/touch_selector"
In screens of sufficient width, set singleChoice mode to enable "activated" touch_selector:
    <ListView
        style="@style/ForecastListStyle"
 

 

UX Layouts Redlines (UI textsize / textAppearance)

48dp translates to a physical size of about 9mm (with some variability). This is comfortably in the range of recommended target sizes (7-10mm) for touchscreen objects and ensures that users will be able to reliably and accurately target them with their fingers.


Text Size Scalable Pixels (sp) Scale-independent Pixels
Definitions for current/old phone??
    Text Size Micro     12sp    ????
    Text Size Small     14sp    "?android:textAppearanceSmall"
    Text Size Medium    18sp    "?android:textAppearanceMedium"
    Text Size Large     22sp    "?android:textAppearanceLarge"

    <TextView
        android:fontFamily="sans-serif-condensed"
        android:textAppearance="?android:textAppearanceSmall"


https://www.google.com/design/spec/style/typography.html
 
A set of six generalized densities:
    ldpi (low) ~120dpi
    mdpi (medium) ~160dpi
    hdpi (high) ~240dpi
    xhdpi (extra-high) ~320dpi
    xxhdpi (extra-extra-high) ~480dpi
    xxxhdpi (extra-extra-extra-high) ~640dpi

Layout Screen Width (layout-swXXXdp): dp - Density-independent Pixels
    320dp: a typical phone screen (240x320 ldpi, 320x480 mdpi, 480x800 hdpi, etc).
    480dp: a tweener tablet like the Streak (480x800 mdpi).
    600dp: a 7” tablet (600x1024 mdpi).
    720dp: a 10” tablet (720x1280 mdpi, 800x1280 mdpi, etc).

Conversion of dp units to screen pixels:
    px = dp * (dpi / 160). For example, on a 240 dpi screen, 1 dp equals 1.5 physical pixels.

px changes based on dpi (device resulution), pt, dp and sp remain (relatively) constant:
    1 inch = 72pt = 160dp = 160sp

Motoroly Defy+ MB256:
Resolution 480 x 854 pixels (~265 ppi pixel density). Screen 3.7 inch. Body 4.21 x 2.32 x 0.53 inch.
screen resolution / display metrics
    DisplayMetrics{density=1.5, densityDpi=240, width=480, height=854, scaledDensity=1.5, xdpi=265.0435, ydpi=264.5317}
densityDpi 240 => DENSITY_HIGH.
    DENSITY_280     Intermediate density for screens that sit between DENSITY_HIGH (240dpi) and DENSITY_XHIGH (320dpi).



 

Maintain Position of a List Item after Screen is Rotated

  • In setOnItemClickListener/onItemClick, remember the view and position.
  • onSaveInstanceState, save it to bundle.
  • onCreateView, read it back. (But don't apply it yet, as listview might not have been populated)
  • onLoadFinished, actually set the smoothScrollToPosition for the view



Reuse/Share View in Wide-screen and Landscape modes

layout/fragment_detail.xml: Regular view for (small) phone.
    layout/fragment_detail_wide.xml: Wider view for wider screens (landscape-mode phones & tables)
    values-land/refs.xml, values-sw600dp/refs.xml: Point/link/references to shared view.
        <item name="fragment_detail" type="layout">@layout/fragment_detail_wide</item>



 

Show a popup message

    Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();



Chronometer (Stop Watch) as Time Piece (Current Time)

        long elapsedTime = SystemClock.elapsedRealtime();
        Calendar calendar = Calendar.getInstance();
        long currTimeInMilli = (calendar.get(Calendar.HOUR_OF_DAY) * 60 * 60 * 1000) +
                (calendar.get(Calendar.MINUTE) * 60 * 1000) +
                (calendar.get(Calendar.SECOND) * 1000) +
                calendar.get(Calendar.MILLISECOND);
        // The value set as setBase is *subtracted* from the chronoBase and that time is shown by Chronometer.
        // Thus, setBase(SystemClock.elapsedRealtime()) sets the clock to 00:00.
        // elapsedTime = 3:00                       Clock (currTime)
        //   setBase(elapsedTime) -> 3:00 - 03:00 = 00:00
        //   setBase(01:00)       -> 3:00 - 01:00 = 02:00
        //   setBase(-01:00)      -> 3:00 + 01:00 = 04:00
        //  baseToSet = elapsedTime - currTime
        mViewHolder.chrono.setBase(elapsedTime - currTimeInMilli);
        mViewHolder.chrono.start();





Android Tools - Shell interface

# Install gradle
"C:\Program Files\Android\Android Studio\gradle\gradle-2.2.1"

# Build app
/cygdrive/c/Program\ Files/Android/Android\ Studio/gradle/gradle-2.2.1/bin/gradle assembleDebug

# Run adb to install (copy/overwrite) apk to device
$ /cygdrive/c/Users/sr/AppData/Local/Android/sdk/platform-tools/adb.exe  install -r app/build/outputs/apk/app-debug-unaligned.apk

# Run adb "am start" to launch app
$ /cygdrive/c/Users/sr/AppData/Local/Android/sdk/platform-tools/adb.exe  shell am start -n com.example.shreekant.sunshine.app/com.example.shreekant.sunshine.app.MainActivity


AVDs:
C:\Users\shreekant\.android\avd\

Genymotion emulator (works with Virtual Box) loads much faster than default Google/Android virtual device emulators.
Genymotion was installed in C:\Users\shreekant\AppData\Local\Genymobile\

ig4icd32.dll is crashing both in Android Studio and Genymotion.
Intel/HP driver was last updated in 2009. So looks like, there may not be any solution to this issue as stated in here: http://gaming.stackexchange.com/questions/213699/minecraft-crashes-on-launch-with-exception-access-violation-problematic-frame


BIG TMP images are kept in C:\Users\sr\AppData\Local\Temp\AndroidEmulator\. -- Delete when done.

Android SDK was installed to C:\Users\sr\AppData\Local\Android\sdk








No comments:

Post a Comment