WeatherInformation: map with progress bar
authorgu.martinm@gmail.com <gu.martinm@gmail.com>
Wed, 17 Sep 2014 17:59:56 +0000 (19:59 +0200)
committergu.martinm@gmail.com <gu.martinm@gmail.com>
Wed, 17 Sep 2014 17:59:56 +0000 (19:59 +0200)
Android/WeatherInformation/res/anim/weather_map_enter_progress.xml [new file with mode: 0644]
Android/WeatherInformation/res/anim/weather_map_exit_progress.xml [new file with mode: 0644]
Android/WeatherInformation/res/layout/weather_map.xml
Android/WeatherInformation/res/layout/weather_map_buttons.xml [new file with mode: 0644]
Android/WeatherInformation/res/layout/weather_map_progress.xml [new file with mode: 0644]
Android/WeatherInformation/src/de/example/exampletdd/MapActivity.java
Android/WeatherInformation/src/de/example/exampletdd/fragment/ErrorDialogFragment.java
Android/WeatherInformation/src/de/example/exampletdd/fragment/map/MapButtonsFragment.java [new file with mode: 0644]
Android/WeatherInformation/src/de/example/exampletdd/fragment/map/MapProgressFragment.java [new file with mode: 0644]

diff --git a/Android/WeatherInformation/res/anim/weather_map_enter_progress.xml b/Android/WeatherInformation/res/anim/weather_map_enter_progress.xml
new file mode 100644 (file)
index 0000000..c747270
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set>
+    <translate xmlns:android="http://schemas.android.com/apk/res/android"
+               android:fromYDelta="100%"
+               android:toYDelta="0%"
+               android:interpolator="@android:anim/decelerate_interpolator"
+               android:duration="@android:integer/config_mediumAnimTime"/>
+</set>
diff --git a/Android/WeatherInformation/res/anim/weather_map_exit_progress.xml b/Android/WeatherInformation/res/anim/weather_map_exit_progress.xml
new file mode 100644 (file)
index 0000000..f425b7e
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set>
+    <translate xmlns:android="http://schemas.android.com/apk/res/android"
+               android:fromYDelta="0%"
+               android:toYDelta="-100%"
+               android:interpolator="@android:anim/accelerate_interpolator"
+               android:duration="@android:integer/config_mediumAnimTime"/>
+</set>
index 3f270e8..2e442c1 100644 (file)
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:layout_below="@+id/weather_map_citycountry_container"
-        android:layout_above="@+id/weather_map_button_savelocation" />
+        android:layout_above="@+id/weather_map_buttons_container" />
 
-
-    <Button
-        android:id="@+id/weather_map_button_savelocation"
-        android:layout_width="wrap_content"
+    <LinearLayout
+        android:id="@+id/weather_map_buttons_container"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_alignParentBottom="true"
         android:layout_alignParentStart="true"
-        android:layout_toStartOf="@+id/weather_map_aux"
-        android:onClick="onClickSaveLocation"
-        android:textAlignment="center"
-        android:text="@string/weather_map_button_savelocation" />
-    <View
-        android:id="@+id/weather_map_aux"
-        android:layout_width="0dp"
-        android:layout_height="1dp"
-        android:layout_centerHorizontal="true" />
-    <Button
-        android:id="@+id/weather_map_button_getlocation"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_alignParentBottom="true"
-        android:layout_alignParentEnd="true"
-        android:layout_toEndOf="@+id/weather_map_aux"
-        android:onClick="onClickGetLocation"
-        android:textAlignment="center"
-        android:enabled="false"
-        android:text="@string/weather_map_button_getlocation" />
+        android:orientation="horizontal"
+        android:baselineAligned="false" >
+    </LinearLayout>
 
 </RelativeLayout>
diff --git a/Android/WeatherInformation/res/layout/weather_map_buttons.xml b/Android/WeatherInformation/res/layout/weather_map_buttons.xml
new file mode 100644 (file)
index 0000000..4f81887
--- /dev/null
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="horizontal"
+    android:baselineAligned="false" >
+    
+       <LinearLayout
+               android:id="@+id/weather_map_button_savelocation_container"
+               android:layout_width="0dp"
+               android:layout_weight="1"
+               android:layout_gravity="center"
+               android:gravity="center"
+               android:layout_height="wrap_content">     
+               <Button
+                       android:id="@+id/weather_map_button_savelocation"
+                       android:layout_width="wrap_content"
+                       android:layout_height="wrap_content"
+                       android:layout_gravity="center"
+                       android:onClick="onClickSaveLocation"
+                       android:textAlignment="center"
+                       android:text="@string/weather_map_button_savelocation" />
+       </LinearLayout>
+       
+       <LinearLayout
+               android:id="@+id/weather_map_button_getlocation_container"
+               android:layout_width="0dp"
+               android:layout_weight="1"
+               android:layout_gravity="center"
+               android:gravity="center"
+               android:layout_height="wrap_content" >
+               <Button
+                       android:id="@+id/weather_map_button_getlocation"
+                       android:layout_width="wrap_content"
+                       android:layout_height="wrap_content"
+                       android:layout_gravity="center"
+                       android:onClick="onClickGetLocation"
+                       android:textAlignment="center"
+                       android:enabled="false"
+                       android:text="@string/weather_map_button_getlocation" />
+               </LinearLayout> 
+</LinearLayout>
diff --git a/Android/WeatherInformation/res/layout/weather_map_progress.xml b/Android/WeatherInformation/res/layout/weather_map_progress.xml
new file mode 100644 (file)
index 0000000..b8eb00e
--- /dev/null
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="horizontal" >
+    
+       <ProgressBar
+           android:id="@+id/weather_map_progressbar"
+           android:layout_width="0dp"
+           android:layout_height="wrap_content"
+           android:layout_gravity="center"
+           android:layout_weight="1"
+           android:indeterminateBehavior="repeat"
+           android:indeterminateDuration="3500"
+           android:indeterminateOnly="true"
+           android:visibility="visible" />
+    
+</LinearLayout>
index 7df3ba0..1625b56 100644 (file)
@@ -1,26 +1,21 @@
 package de.example.exampletdd;
 
-import java.io.IOException;
-import java.util.List;
-import java.util.Locale;
-
 import android.app.ActionBar;
 import android.app.Activity;
 import android.app.Dialog;
-import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.IntentSender.SendIntentException;
-import android.location.Address;
 import android.location.Geocoder;
 import android.location.Location;
 import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Bundle;
 import android.support.v4.app.DialogFragment;
+import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentActivity;
-import android.support.v4.content.LocalBroadcastManager;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
 import android.util.Log;
 import android.view.View;
 import android.widget.Button;
@@ -43,6 +38,8 @@ import com.google.android.gms.maps.model.Marker;
 import com.google.android.gms.maps.model.MarkerOptions;
 
 import de.example.exampletdd.fragment.ErrorDialogFragment;
+import de.example.exampletdd.fragment.map.MapButtonsFragment;
+import de.example.exampletdd.fragment.map.MapProgressFragment;
 import de.example.exampletdd.gms.GPlayServicesErrorDialogFragment;
 import de.example.exampletdd.model.DatabaseQueries;
 import de.example.exampletdd.model.WeatherLocation;
@@ -50,21 +47,19 @@ import de.example.exampletdd.model.WeatherLocation;
 public class MapActivity extends FragmentActivity implements
                                                                        GoogleApiClient.ConnectionCallbacks,
                                                                        GoogleApiClient.OnConnectionFailedListener,
-                                                                       LocationListener {
+                                                                       LocationListener,
+                                                                       MapProgressFragment.TaskCallbacks {
        private static final String TAG = "MapActivity";
        // Request code to use when launching the resolution activity
     private static final int REQUEST_RESOLVE_ERROR = 1001;
+    private static final String PROGRESS_FRAGMENT_TAG = "PROGRESS_FRAGMENT";
+    private static final String BUTTONS_FRAGMENT_TAG = "BUTTONS_FRAGMENT";
     private WeatherLocation mRestoreUI;
-    private BroadcastReceiver mReceiver;
        
     // Google Play Services Map
     private GoogleMap mMap;
     // TODO: read and store from different threads? Hopefully always from UI thread.
     private Marker mMarker;
-    // IT IS IMPOSSIBLE IF SCREEN MAY ROTATE!!!! ANDROID SUCKS!!!!
-//    private ProgressBar mActivityIndicator;
-//    // true progress bar visible/false progress bar gone
-//    private boolean mIsProgressBarVisible;
     
     // Google Play Services Location
     private GoogleApiClient mGoogleApiClient;
@@ -101,17 +96,6 @@ public class MapActivity extends FragmentActivity implements
         this.mMap.setMyLocationEnabled(false);
         this.mMap.getUiSettings().setCompassEnabled(false);
         this.mMap.setOnMapLongClickListener(new MapActivityOnMapLongClickListener(this));
-        
-        // Progress bar. View and status (we keep status even after screen rotations.
-//        this.mActivityIndicator = (ProgressBar) this.findViewById(R.id.weather_map_progress);
-//        if (savedInstanceState != null) {
-//             this.mIsProgressBarVisible = savedInstanceState.getBoolean("mIsProgressBarVisible", false);
-//        }
-//        if (this.mIsProgressBarVisible) {
-//             this.mActivityIndicator.setVisibility(View.VISIBLE);
-//        } else {
-//             this.mActivityIndicator.setVisibility(View.GONE);
-//        }
     }
     
     @Override
@@ -130,7 +114,7 @@ public class MapActivity extends FragmentActivity implements
     @Override
     public void onResume() {
         super.onResume();
-        
+
         final ActionBar actionBar = this.getActionBar();
         // TODO: string resource
         actionBar.setTitle("Mark your location");
@@ -141,7 +125,22 @@ public class MapActivity extends FragmentActivity implements
                weatherLocation = this.mRestoreUI;
                // just once
                this.mRestoreUI = null;
-        } else {
+        } else if (this.mMarker != null ) {
+               final TextView city = (TextView) this.findViewById(R.id.weather_map_city);
+            final TextView country = (TextView) this.findViewById(R.id.weather_map_country);
+            final String cityString = city.getText().toString();
+            final String countryString = country.getText().toString();
+            
+            final LatLng point = this.mMarker.getPosition();
+            double latitude = point.latitude;
+            double longitude = point.longitude;
+
+            weatherLocation = new WeatherLocation()
+                       .setCity(cityString)
+                       .setCountry(countryString)
+                       .setLatitude(latitude)
+                       .setLongitude(longitude);
+       } else {
                final DatabaseQueries query = new DatabaseQueries(this.getApplicationContext());
                weatherLocation = query.queryDataBase();
         }
@@ -151,6 +150,28 @@ public class MapActivity extends FragmentActivity implements
         }
     }
     
+    /**
+     * I am not using fragment transactions in the right way. But I do not know other way for doing what I am doing.
+     * 
+     * {@link http://stackoverflow.com/questions/16265733/failure-delivering-result-onactivityforresult}
+     */
+    @Override
+    public void onPostResume() {
+       super.onPostResume();
+       
+       final FragmentManager fm = getSupportFragmentManager();
+       final Fragment progressFragment = fm.findFragmentByTag(PROGRESS_FRAGMENT_TAG);
+       if (progressFragment == null) {
+                this.addButtonsFragment();
+       } else {
+               this.removeProgressFragment();
+               final Bundle bundle = progressFragment.getArguments();
+               double latitude = bundle.getDouble("latitude");
+               double longitude = bundle.getDouble("longitude");
+               this.addProgressFragment(latitude, longitude);
+       }
+    }
+    
     @Override
     public void onSaveInstanceState(final Bundle savedInstanceState) {
        // Save UI state
@@ -172,15 +193,8 @@ public class MapActivity extends FragmentActivity implements
                        .setLongitude(longitude);
             savedInstanceState.putSerializable("WeatherLocation", location);
         }
-       // Save progress bar status.
-//     savedInstanceState.putBoolean("mIsProgressBarVisible", this.mIsProgressBarVisible);
-               
-       // Google Play Services
-       // To keep track of the boolean across activity restarts (such as when
-       // the user rotates the screen), save the boolean in the activity's saved
-       // instance data using onSaveInstanceState():
-       savedInstanceState.putBoolean("mResolvingError", this.mResolvingError);
        
+       savedInstanceState.putBoolean("mResolvingError", this.mResolvingError);
         
        super.onSaveInstanceState(savedInstanceState);
     }
@@ -256,81 +270,6 @@ public class MapActivity extends FragmentActivity implements
         this.mMap.animateCamera(CameraUpdateFactory.zoomTo(8), 2000, null);
     }
     
-    private class GetAddressTask extends AsyncTask<Object, Void, WeatherLocation> {
-        private static final String TAG = "GetAddressTask";
-        // Store the context passed to the AsyncTask when the system instantiates it.
-        private final Context localContext;
-
-        private GetAddressTask(final Context context) {
-               this.localContext = context;    
-        }
-        
-        @Override
-        protected void onPreExecute() {
-               // TODO: The same with Overview and Current? I guess so...
-               // Show the activity indicator
-//             final MapActivity activity = (MapActivity) this.localContext;
-//             activity.mActivityIndicator.setVisibility(View.VISIBLE);
-//             activity.mIsProgressBarVisible = true;
-        }
-        
-        @Override
-        protected WeatherLocation doInBackground(final Object... params) {
-            final double latitude = (Double) params[0];
-            final double longitude = (Double) params[1];
-
-            WeatherLocation weatherLocation = null;
-            try {
-               weatherLocation = this.getLocation(latitude, longitude);
-            } catch (final IOException e) {
-                Log.e(TAG, "GetAddressTask doInBackground exception: ", e);
-            }
-
-            return weatherLocation;
-        }
-
-        @Override
-        protected void onPostExecute(final WeatherLocation weatherLocation) {
-               // TODO: Is AsyncTask calling this method even when RunTimeException in doInBackground method?
-               // I hope so, otherwise I must catch(Throwable) in doInBackground method :(
-               if (weatherLocation == null) {
-                       // Nothing to do
-                       // TODO: Should I show some error message? I am not doing it on WP8 Should I do it on WP8?
-                       return;
-               }
-
-            // Call updateUI on the UI thread.
-            final Intent weatherLocationData = new Intent("de.example.exampletdd.UPDATEWEATHERLOCATION");
-            weatherLocationData.putExtra("weatherLocation", weatherLocation);
-            LocalBroadcastManager.getInstance(this.localContext).sendBroadcastSync(weatherLocationData);
-        }
-        
-        private WeatherLocation getLocation(final double latitude, final double longitude) throws IOException {
-               // TODO: i18n Locale.getDefault()
-            final Geocoder geocoder = new Geocoder(this.localContext, Locale.US);
-            final List<Address> addresses = geocoder.getFromLocation(latitude, longitude, 1);
-
-            // Default values
-            String city = this.localContext.getString(R.string.city_not_found);
-            String country = this.localContext.getString(R.string.country_not_found); 
-            if (addresses != null && addresses.size() > 0) {
-               if (addresses.get(0).getLocality() != null) {
-                       city = addresses.get(0).getLocality();
-               }
-               if(addresses.get(0).getCountryName() != null) {
-                       country = addresses.get(0).getCountryName();
-               }       
-            }
-
-            return new WeatherLocation()
-                       .setLatitude(latitude)
-                       .setLongitude(longitude)
-                       .setCity(city)
-                       .setCountry(country);
-        }
-
-    }
-    
     private class MapActivityOnMapLongClickListener implements OnMapLongClickListener {
        // Store the context passed to the AsyncTask when the system instantiates it.
         private final Context localContext;
@@ -359,13 +298,14 @@ public class MapActivity extends FragmentActivity implements
      *
      */
     private void getAddressAndUpdateUI(final double latitude, final double longitude) {
-
         // In Gingerbread and later, use Geocoder.isPresent() to see if a geocoder is available.
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD && Geocoder.isPresent()) {
-               // Start the background task
-               final GetAddressTask geocoderAsyncTask = new GetAddressTask(this);
-               geocoderAsyncTask.execute(latitude, longitude);
+               this.removeButtonsFragment();
+               this.removeProgressFragment();
+               this.addProgressFragment(latitude, longitude);
         } else {
+               this.removeProgressFragment();
+               this.addButtonsFragment();
                // No geocoder is present. Issue an error message.
                // TODO: string resource
             Toast.makeText(this, "Cannot get address. No geocoder available.", Toast.LENGTH_LONG).show();
@@ -436,36 +376,6 @@ public class MapActivity extends FragmentActivity implements
         if (!mResolvingError) {
             mGoogleApiClient.connect();
         }
-
-        this.mReceiver = new BroadcastReceiver() {
-
-                       @Override
-                       public void onReceive(final Context context, final Intent intent) {
-                               final String action = intent.getAction();
-                               if (action.equals("de.example.exampletdd.UPDATEWEATHERLOCATION")) {
-                                       final WeatherLocation weatherLocation = (WeatherLocation) intent.getSerializableExtra("weatherLocation");
-
-//                             MapActivity.this.mActivityIndicator.setVisibility(View.GONE);
-//                             MapActivity.this.mIsProgressBarVisible = false;
-
-                               // TODO: Is AsyncTask calling this method even when RunTimeException in doInBackground method?
-                               // I hope so, otherwise I must catch(Throwable) in doInBackground method :(
-                           if (weatherLocation == null) {
-                               final DialogFragment newFragment = ErrorDialogFragment.newInstance(R.string.error_dialog_location_error);
-                               newFragment.setRetainInstance(true);
-                               newFragment.show(MapActivity.this.getSupportFragmentManager(), "errorDialog");
-                               return;
-                           }
-
-                           MapActivity.this.updateUI(weatherLocation);
-                               }
-                       }
-        };
-
-        // Register receiver
-        final IntentFilter filter = new IntentFilter();
-        filter.addAction("de.example.exampletdd.UPDATEWEATHERLOCATION");
-        LocalBroadcastManager.getInstance(this.getApplicationContext()).registerReceiver(this.mReceiver, filter);
     }
     
     /**
@@ -481,8 +391,6 @@ public class MapActivity extends FragmentActivity implements
        mGoogleApiClient.unregisterConnectionFailedListener(this);
         mGoogleApiClient.disconnect();
 
-        LocalBroadcastManager.getInstance(this.getApplicationContext()).unregisterReceiver(this.mReceiver);
-
         super.onStop();
     }
     
@@ -517,8 +425,13 @@ public class MapActivity extends FragmentActivity implements
     
        @Override
        public void onConnected(final Bundle bundle) {
-               final Button getLocationButton = (Button) this.findViewById(R.id.weather_map_button_getlocation);
-       getLocationButton.setEnabled(true);
+               final FragmentManager fm = getSupportFragmentManager();
+       Fragment buttonsFragment = fm.findFragmentByTag(BUTTONS_FRAGMENT_TAG);
+       if (buttonsFragment != null) {
+               final Button getLocationButton = (Button) this.findViewById(R.id.weather_map_button_getlocation);
+               getLocationButton.setEnabled(true);
+       }
+
                // TODO: string resource
                Toast.makeText(this, "Connected to Google Play Services.", Toast.LENGTH_SHORT).show();
        }
@@ -644,6 +557,7 @@ public class MapActivity extends FragmentActivity implements
     
     /* Called from GPlayServicesErrorDialogFragment when the dialog is dismissed. */
     public void onDialogDismissed() {
+       // IT DOES NOT WORK WHEN SCREEN ROTATES. ANDROID EXAMPLES SUCK!!!
         mResolvingError = false;
     }
     
@@ -711,4 +625,88 @@ public class MapActivity extends FragmentActivity implements
                // TODO: May location not be null?
                this.getAddressAndUpdateUI(location.getLatitude(), location.getLongitude());
        }
+
+       /*****************************************************************************************************
+        *
+        *                                                      MapProgressFragment.TaskCallbacks
+        *
+        *****************************************************************************************************/
+       @Override
+       public void onPostExecute(WeatherLocation weatherLocation) {
+
+        if (weatherLocation == null) {
+               this.removeProgressFragment();
+            final DialogFragment newFragment = ErrorDialogFragment.newInstance(R.string.error_dialog_location_error);
+            newFragment.setRetainInstance(true);
+            newFragment.show(this.getSupportFragmentManager(), "errorDialog");
+        } else {
+               this.updateUI(weatherLocation);
+               this.removeProgressFragment();
+        }
+        this.addButtonsFragment();
+       }
+
+       /*****************************************************************************************************
+        *
+        *                                                      MapProgressFragment
+        * I am not using fragment transactions in the right way. But I do not know other way for doing what I am doing.
+     * Android sucks.
+     *
+     * "Avoid performing transactions inside asynchronous callback methods." :(
+     * see: http://stackoverflow.com/questions/16265733/failure-delivering-result-onactivityforresult
+     * see: http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html
+     * How do you do what I am doing in a different way without using fragments?
+        *****************************************************************************************************/
+       
+       private void addProgressFragment(final double latitude, final double longitude) {
+       final Fragment progressFragment = new MapProgressFragment();
+       progressFragment.setRetainInstance(true);
+       final Bundle args = new Bundle();
+       args.putDouble("latitude", latitude);
+       args.putDouble("longitude", longitude);
+       progressFragment.setArguments(args);
+       
+       final FragmentManager fm = this.getSupportFragmentManager();
+       final FragmentTransaction fragmentTransaction = fm.beginTransaction();
+       fragmentTransaction.setCustomAnimations(R.anim.weather_map_enter_progress, R.anim.weather_map_exit_progress);
+       fragmentTransaction.add(R.id.weather_map_buttons_container, progressFragment, PROGRESS_FRAGMENT_TAG).commit();
+       fm.executePendingTransactions();
+       }
+       
+       private void removeProgressFragment() {
+       final FragmentManager fm = this.getSupportFragmentManager();
+       final Fragment progressFragment = fm.findFragmentByTag(PROGRESS_FRAGMENT_TAG);
+       if (progressFragment != null) {
+               final FragmentTransaction fragmentTransaction = fm.beginTransaction();
+               fragmentTransaction.remove(progressFragment).commit();
+               fm.executePendingTransactions();
+       }
+       }
+       
+       private void addButtonsFragment() {
+               final FragmentManager fm = this.getSupportFragmentManager();
+       Fragment buttonsFragment = fm.findFragmentByTag(BUTTONS_FRAGMENT_TAG);
+       if (buttonsFragment == null) {
+               buttonsFragment = new MapButtonsFragment();
+               buttonsFragment.setRetainInstance(true);
+               final FragmentTransaction fragmentTransaction = fm.beginTransaction();
+               fragmentTransaction.setCustomAnimations(R.anim.weather_map_enter_progress, R.anim.weather_map_exit_progress);
+               fragmentTransaction.add(R.id.weather_map_buttons_container, buttonsFragment, BUTTONS_FRAGMENT_TAG).commit();
+               fm.executePendingTransactions();
+       }
+       if (mGoogleApiClient.isConnected()) {
+               final Button getLocationButton = (Button) this.findViewById(R.id.weather_map_button_getlocation);
+               getLocationButton.setEnabled(true);
+       }
+       }
+       
+       private void removeButtonsFragment() {
+       final FragmentManager fm = this.getSupportFragmentManager();
+       final Fragment buttonsFragment = fm.findFragmentByTag(BUTTONS_FRAGMENT_TAG);
+       if (buttonsFragment != null) {
+               final FragmentTransaction fragmentTransaction = fm.beginTransaction();
+               fragmentTransaction.remove(buttonsFragment).commit();
+               fm.executePendingTransactions();
+       }
+       }
 }
index 33876e2..2728bc6 100644 (file)
@@ -35,4 +35,12 @@ public class ErrorDialogFragment extends DialogFragment {
             }
         }).create();
     }
+    
+    @Override
+    public void onDestroyView() {
+       if (getDialog() != null && getRetainInstance()) {
+               getDialog().setDismissMessage(null);
+       }
+       super.onDestroyView();
+    }
 }
\ No newline at end of file
diff --git a/Android/WeatherInformation/src/de/example/exampletdd/fragment/map/MapButtonsFragment.java b/Android/WeatherInformation/src/de/example/exampletdd/fragment/map/MapButtonsFragment.java
new file mode 100644 (file)
index 0000000..a063eb5
--- /dev/null
@@ -0,0 +1,31 @@
+package de.example.exampletdd.fragment.map;
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import de.example.exampletdd.R;
+
+public class MapButtonsFragment extends Fragment {
+       
+    @Override
+    public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
+                             final Bundle savedInstanceState) {
+    
+       // Inflate the layout for this fragment
+        return inflater.inflate(R.layout.weather_map_buttons, container, false);
+    }
+    
+    /**
+     * This method will only be called once when the retained
+     * Fragment is first created.
+     */
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+       super.onCreate(savedInstanceState);
+       
+       // Retain this fragment across configuration changes.
+       this.setRetainInstance(true);
+    }
+}
diff --git a/Android/WeatherInformation/src/de/example/exampletdd/fragment/map/MapProgressFragment.java b/Android/WeatherInformation/src/de/example/exampletdd/fragment/map/MapProgressFragment.java
new file mode 100644 (file)
index 0000000..08dc5e1
--- /dev/null
@@ -0,0 +1,158 @@
+package de.example.exampletdd.fragment.map;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Locale;
+
+import android.app.Activity;
+import android.content.Context;
+import android.location.Address;
+import android.location.Geocoder;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import de.example.exampletdd.R;
+import de.example.exampletdd.model.WeatherLocation;
+
+/**
+ * {@link http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html}
+ *
+ */
+public class MapProgressFragment extends Fragment {
+
+       /**
+        * 
+        * Callback interface through which the fragment will report the
+        * task's progress and results back to the Activity.
+        */
+       public static interface TaskCallbacks {
+               void onPostExecute(final WeatherLocation weatherLocation);
+       }
+       
+       private TaskCallbacks mCallbacks;
+       
+    @Override
+    public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
+                             final Bundle savedInstanceState) {
+    
+       // Inflate the layout for this fragment
+        return inflater.inflate(R.layout.weather_map_progress, container, false);
+    }
+    
+    /**
+     * This method will only be called once when the retained
+     * Fragment is first created.
+     */
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+       super.onCreate(savedInstanceState);
+       
+       // Retain this fragment across configuration changes.
+       this.setRetainInstance(true);
+       
+       final Bundle bundle = this.getArguments();
+       double latitude = bundle.getDouble("latitude");
+       double longitude = bundle.getDouble("longitude");
+       
+       // Create and execute the background task.
+       new GetAddressTask(this.getActivity().getApplicationContext()).execute(latitude, longitude);
+    }
+    
+       /**
+        * Hold a reference to the parent Activity so we can report the
+        * task's current progress and results. The Android framework 
+        * will pass us a reference to the newly created Activity after 
+        * each configuration change.
+        */
+       @Override
+       public void onAttach(final Activity activity) {
+               super.onAttach(activity);
+               mCallbacks = (TaskCallbacks) activity;
+       }
+       
+       /**
+        * Set the callback to null so we don't accidentally leak the 
+        * Activity instance.
+        */
+//     @Override
+//     public void onDetach() {
+//             super.onDetach();
+//             mCallbacks = null;
+//     }
+       
+       /**
+        * I am not using onDetach because there are problems when my activity goes to background.
+        * 
+        * {@link http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html}
+        */
+       @Override
+       public void onPause() {
+               super.onPause();
+               mCallbacks = null;
+       }
+    
+    private class GetAddressTask extends AsyncTask<Object, Void, WeatherLocation> {
+        private static final String TAG = "GetAddressTask";
+        // Store the context passed to the AsyncTask when the system instantiates it.
+        private final Context localContext;
+
+        private GetAddressTask(final Context context) {
+               this.localContext = context;    
+        }
+        
+        @Override
+        protected WeatherLocation doInBackground(final Object... params) {
+            final double latitude = (Double) params[0];
+            final double longitude = (Double) params[1];
+
+            WeatherLocation weatherLocation = null;
+            try {
+               weatherLocation = this.getLocation(latitude, longitude);
+            } catch (final IOException e) {
+                Log.e(TAG, "GetAddressTask doInBackground exception: ", e);
+            }
+
+            return weatherLocation;
+        }
+
+        @Override
+        protected void onPostExecute(final WeatherLocation weatherLocation) {
+               // TODO: Is AsyncTask calling this method even when RunTimeException in doInBackground method?
+               // I hope so, otherwise I must catch(Throwable) in doInBackground method :(
+            // Call updateUI on the UI thread.
+               if (mCallbacks != null) {
+                       mCallbacks.onPostExecute(weatherLocation);
+               }
+        }
+        
+        private WeatherLocation getLocation(final double latitude, final double longitude) throws IOException {
+               // TODO: i18n Locale.getDefault()
+            final Geocoder geocoder = new Geocoder(this.localContext, Locale.US);
+            final List<Address> addresses = geocoder.getFromLocation(latitude, longitude, 1);
+
+            // Default values
+            String city = this.localContext.getString(R.string.city_not_found);
+            String country = this.localContext.getString(R.string.country_not_found); 
+            if (addresses != null && addresses.size() > 0) {
+               if (addresses.get(0).getLocality() != null) {
+                       city = addresses.get(0).getLocality();
+               }
+               if(addresses.get(0).getCountryName() != null) {
+                       country = addresses.get(0).getCountryName();
+               }       
+            }
+
+            return new WeatherLocation()
+                       .setLatitude(latitude)
+                       .setLongitude(longitude)
+                       .setCity(city)
+                       .setCountry(country);
+        }
+
+    }
+}