Search code examples
google-mapsandroid-fragmentsandroid-syncadapteryelpandroid-gps

How to receive GPS Location and inflate Fragment MapView to programmatically add markers with SyncAdapter


Well, after reading many examples and many posts in this site I have solved some problems and finally I managed to have the fragment responding and my syncAdapter working. But after all these days trying many things I can't yet see the map. All I have is a blank screen. I've tried a few things but it is not working for me. I'm hitting the wall with this and I need to keep moving with my project.
Could someone guide me how to make this work. I'd really appreciate it. I'd just please ask you to notice all the elements I have because I've been going in circles, when I tried to change something the other stuff that was working fine breaks.

This app has as you see below: A Location request (Main Activity) which syncs on Change and returns GPS Lat and Lon. After that it brings the Fragment and once there I first Sync the data using the GPS lat and lon sending these to be stored in the database with Schematic Library. The syncAdapter uses the GPS lat and lon to pass to Yelp which does (as far as I understand) Async calls.

fyi, I used posts like this one but it doesn't work for me.

I'm being careful when I import android.support.v4.app.xxxx or simple import android.app.xxx because it messes up something else. The way is running right now nothing breaks but I just can't see the map yet.

What I've tried:
(With results like, duplicated id, Nullpointer) Few posts say to use getChildFragmentManager, to use onDestroyView, to use class SupportMapFragment but nothing works.
Here those posts: one, two, three, four

After reading those posts I tweaked the code as below with error "NullPointerException in the SupportMapFragment". I tried also plain MapFragment but nothing.


I won't be able to thank you enough for this.
Cheers


Tweaked like this:(had to change the minSdkVersion to 17 for this part)


in Fragment

 @Override
    public void onDestroyView()
    {
        super.onDestroyView();
        Fragment fragment = (getActivity().getSupportFragmentManager().findFragmentById(R.id.thismap));
        FragmentTransaction ft = getActivity().getSupportFragmentManager().beginTransaction();
        ft.remove(fragment);
        ft.commit();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

        //getLoaderManager().initLoader(CURSOR_LOADER_ID, null, this);


                SupportMapFragment mapFragment = (SupportMapFragment) this.getChildFragmentManager()
                .findFragmentById(R.id.thismap);
        mapFragment.getMapAsync(this);
return null;
}

Tried this for yelp_google_map.xml (fragment alone, nothing else)

<fragment android:id="@+id/thismap"
          android:name="com.google.android.gms.maps.SupportMapFragment"
          xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:map="http://schemas.android.com/apk/res-auto"
          xmlns:tools="http://schemas.android.com/tools"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          tools:context="mem.edu.joshua.MapsActivity"/>




Whole app without the above tweak
Gradle

apply plugin: 'com.android.application'
apply plugin: 'android-apt'
//apply plugin: 'com.google.gms.google-services'

android {
    compileSdkVersion 24
    buildToolsVersion "24.0.0"

    defaultConfig {
        applicationId "mem.edu.joshua"
        minSdkVersion 16
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"
        multiDexEnabled true
    }

    dexOptions {
        preDexLibraries = false
        javaMaxHeapSize "4g"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    buildTypes.each {
        it.buildConfigField 'String', 'YELP_CONSUMER_KEY', YelpConsumerKey
        it.buildConfigField 'String', 'YELP_CONSUMER_SECRET', YelpConsumerSecret
        it.buildConfigField 'String', 'YELP_TOKEN', YelpToken
        it.buildConfigField 'String', 'YELP_TOKEN_SECRET', YelpTokenSecret
    }

}

repositories {
    mavenCentral()
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    apt 'net.simonvt.schematic:schematic-compiler:0.6.3'

    compile 'net.simonvt.schematic:schematic:0.6.3'
    compile 'com.android.support:appcompat-v7:24.1.1'
    compile 'com.google.firebase:firebase-ads:9.0.2'
    //compile 'com.google.android.gms:play-services-maps:9.2.1'
    compile 'com.google.android.gms:play-services-maps:9.0.2'
    compile 'com.google.android.gms:play-services-location:9.0.2'
    compile 'com.yelp.clientlib:yelp-android:2.0.0'
    compile 'org.scribe:scribe:1.3.7'
    //compile 'com.google.android.gms:play-services:9.2.1'
}

Manifest

<?xml version="1.0" encoding="utf-8"?>
<manifest package="mem.edu.joshua"
          xmlns:android="http://schemas.android.com/apk/res/android">

    <!--
         The ACCESS_COARSE/FINE_LOCATION permissions are not required to use
         Google Maps Android API v2, but you must specify either coarse or fine
         location permissions for the 'MyLocation' functionality. 
    -->
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
    <uses-permission android:name="android.permission.READ_SYNC_SETTINGS"/>
    <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/Theme.AppCompat">
        <uses-library android:name="com.google.android.maps"/>

        <!--
             The API key for Google Maps-based APIs is defined as a string resource.
             (See the file "res/values/google_maps_api.xml").
             Note that the API key is linked to the encryption key used to sign the APK.
             You need a different API key for each encryption key, including the release key that is used to
             sign the APK for publishing.
             You can define the keys for the debug and release targets in src/debug/ and src/release/. 
        -->
        <meta-data
            android:name="com.google.android.geo.API_KEY"
            android:value="@string/google_maps_key"/>

        <activity
            android:name=".MainActivity"
            android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

        <!--<activity-->
            <!--android:name=".MapsActivity"-->
            <!--android:label="@string/app_name">-->
            <!--<meta-data-->
                <!--android:name="android.support.PARENT_ACTIVITY"-->
                <!--android:value="mem.edu.joshua.MainActivity"/>-->

            <!--<intent-filter>-->
                <!--<action android:name="android.intent.action.MAIN"/>-->

                <!--<category android:name="android.intent.category.LAUNCHER"/>-->
            <!--</intent-filter>-->
        <!--</activity>-->

        <!-- android:accountType="com.clashtoolkit.clashtoolkit" -->
        <service android:name=".sync.AuthenticatorService">
            <intent-filter>
                <action android:name="android.accounts.AccountAuthenticator"/>
            </intent-filter>

            <meta-data
                android:name="android.accounts.AccountAuthenticator"
                android:resource="@xml/authenticator"/>
        </service>

        <!-- The SyncAdapter service -->
        <!-- android:allowParallelSyncs="true" -->
        <service
            android:name=".sync.SyncService"
            android:exported="true">
            <intent-filter>
                <action android:name="android.content.SyncAdapter"/>
            </intent-filter>

            <meta-data
                android:name="android.content.SyncAdapter"
                android:resource="@xml/syncadapter"/>
        </service>

        <provider
            android:name=".data.generated.QuoteProvider"
            android:authorities="mem.edu.joshua.data.QuoteProvider"
            android:exported="false"/>

    </application>

</manifest>

In mainActivity.java

@Override
    public void onConnected(@Nullable Bundle bundle) {

LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this);

        mLocationRequest = LocationRequest.create();
        mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
        mLocationRequest.setInterval(60000);


        try {

            LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient,mLocationRequest, (LocationListener) this);

            if (mLocationRequest != null) {
                Log.e(LOG_TAG, "google working");



//                FragmentManager manager = getSupportFragmentManager();
//                FragmentTransaction transaction = manager.beginTransaction();
//                SupportMapFragment fragment = new SupportMapFragment();
//                transaction.add(R.id.mapView, fragment);
//                transaction.commit();

                LocationManager service = (LocationManager) getSystemService(LOCATION_SERVICE);
                Criteria criteria = new Criteria();
                String provider = service.getBestProvider(criteria, false);
                Location location = service.getLastKnownLocation(provider);


                coord.setLon(location.getLongitude());
                coord.setLat(location.getLatitude());

                sPref.saveCoordBody("lat", coord.getLat());
                sPref.saveCoordBody("lon", coord.getLon());

                    getSupportFragmentManager()
                            .beginTransaction()
                            .add(R.id.container, new MapsActivity(), MapsActivity.LOG_TAG)
                            .disallowAddToBackStack()
                            .commit();


            }

        }catch  (SecurityException e) {
            Log.e(LOG_TAG, e.getMessage(), e);
            e.printStackTrace();
        }


    }

In MapsActivity.java (Fragment -> import android.support.v4.app.FragmentActivity;)

public class MapsActivity extends Fragment implements OnMapReadyCallback, LoaderManager.LoaderCallbacks<Cursor> {

GoogleApiClient.ConnectionCallbacks,
        public static final String LOG_TAG = MapsActivity.class.getSimpleName();

    private static final int CURSOR_LOADER_ID = 0;

    private Context mContext;


    private MapCursor mMapCursor;
    protected Location mLastLocation;
    private Cursor initQueryCursor;
    private GetSet coord;
    private AppPreferences sPref;
    private LatLng home;
    private GoogleMap mMap;
    MapView mMapView;

    private GoogleMap googleMap;

        private static final String FRAGMENT_LISTS =
                "net.simonvt.schematic.samples.ui.SampleActivity.LISTS";




    public MapsActivity() {
    }


    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);


        sPref = new AppPreferences(getActivity());

        SyncAdapter.syncImmediately(getActivity(),
                sPref.getCoordBody("lat"),
                sPref.getCoordBody("lon"));

    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

        //getLoaderManager().initLoader(CURSOR_LOADER_ID, null, this);


        View v = inflater.inflate(R.layout.yelp_google_map, container,
                false);


        mMapView = (MapView) v.findViewById(R.id.mapView);

        mMapView.onCreate(savedInstanceState);

        mMapView.onResume();

        mMapView.getMapAsync(this);

        return v;

    }


    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        return new CursorLoader(getActivity(), QuoteProvider.Quotes.CONTENT_URI,
                new String[]{ QuoteColumns._ID, QuoteColumns.DISPLAY_ADDRESS, QuoteColumns.DISPLAY_PHONE,
                        QuoteColumns.RATING, QuoteColumns.URL, QuoteColumns.POSTAL_CODE, QuoteColumns.ID_BUSINESS_NAME,
                        QuoteColumns.LATITUDE,
                        QuoteColumns.LOGITUDE},
                null,
                null,
                null);
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {

        if (loader != null) {
            //     mMapCursor.swapCursor(data);
            initQueryCursor = data;
        }
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {

    }


    @Override
    public void onResume() {
        super.onResume();
        mMapView.onResume();
    }


    @Override
    public void onMapReady(GoogleMap googleMap) {
        LatLngBounds.Builder builder = new LatLngBounds.Builder();

        mMap = googleMap;

        initQueryCursor = getContext().getContentResolver().query(QuoteProvider.Quotes.CONTENT_URI,
                new String[]{QuoteColumns.LATITUDE, QuoteColumns.LOGITUDE, QuoteColumns.ID_BUSINESS_NAME}, null,
                null, null);


        if (initQueryCursor != null) {

            DatabaseUtils.dumpCursor(initQueryCursor);
            initQueryCursor.moveToFirst();
            for (int i = 0; i < initQueryCursor.getCount(); i++) {

                home = new LatLng(Double.valueOf(initQueryCursor.getString(initQueryCursor.getColumnIndex("latitude"))),
                        Double.valueOf(initQueryCursor.getString(initQueryCursor.getColumnIndex("longitude"))));
                mMap.addMarker(new MarkerOptions().position(home).
                        title(initQueryCursor.getString(initQueryCursor.getColumnIndex("id_bussines_name"))));

                initQueryCursor.moveToNext();

                builder.include(home);
            }
            if(home!=null) {
                mMap.moveCamera(CameraUpdateFactory.newLatLngBounds(builder.build(), 91, 91, 42));
            }
        }
    }
}

My yelp_google_map.xml

<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">

    <!--<include-->
        <!--android:id="@+id/map_toolbar"-->
        <!--layout="@layout/app_bar" />-->



    <LinearLayout

        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">


        <com.google.android.gms.maps.MapView
            android:id="@+id/mapView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            class="com.google.android.gms.maps.MapFragment" />
        <!--was SupportMapFragment-->
    </LinearLayout>
</LinearLayout>

Solution

  • After desperate attempts I found the solution given in a similar thread by Mrigank here I believe after my tweak in the code even though it is being inflated the parent container is somehow null.

    So My yelp_google_map.xml goes like this.

    <RelativeLayout 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"
                    tools:context="mem.edu.joshua.MapsActivity"
                    android:background="#009688">
    
    
        <fragment
            android:id="@+id/map"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            class="com.google.android.gms.maps.SupportMapFragment" />
    
    </RelativeLayout>
    

    I got rid of the the @Override public void onDestroyView part

    onCreateView stays like this

    top variables:
    private SupportMapFragment sMap;
    private FragmentManager fm;
    private GoogleMap mMap;

    @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
    
            getLoaderManager().initLoader(CURSOR_LOADER_ID, null, this);
            View v = inflater.inflate(R.layout.yelp_google_map, container, false);
    
            fm=getChildFragmentManager();
    
            sMap = ((SupportMapFragment) fm.findFragmentById(R.id.map));
    
            mMap = sMap.getMap();
    
    
            initQueryCursor = getActivity().getContentResolver().query(QuoteProvider.Quotes.CONTENT_URI,
                    new String[]{QuoteColumns.LATITUDE, QuoteColumns.LOGITUDE, QuoteColumns.ID_BUSINESS_NAME}, null,
                    null, null);
    
            LatLngBounds.Builder builder = new LatLngBounds.Builder();
            if (initQueryCursor != null) {
    
                DatabaseUtils.dumpCursor(initQueryCursor);
                initQueryCursor.moveToFirst();
                for (int i = 0; i < initQueryCursor.getCount(); i++) {
    
                    home = new LatLng(Double.valueOf(initQueryCursor.getString(initQueryCursor.getColumnIndex("latitude"))),
                            Double.valueOf(initQueryCursor.getString(initQueryCursor.getColumnIndex("longitude"))));
                    mMap.addMarker(new MarkerOptions().position(home).
                            title(initQueryCursor.getString(initQueryCursor.getColumnIndex("id_bussines_name"))));
    
                    initQueryCursor.moveToNext();
    
                    builder.include(home);
                }
                if(home!=null) {
                    mMap.moveCamera(CameraUpdateFactory.newLatLngBounds(builder.build(), 91, 91, 42));
                }
            }
    
            return v;
    
        }
    

    Added all this in the same Fragment java file

     @Override
            public void onResume() {
                super.onResume();
                sMap.onResume();
            }
    
            @Override
            public void onPause() {
                super.onPause();
                sMap.onPause();
            }
    
            @Override
            public void onDestroy() {
                super.onDestroy();
                sMap.onDestroy();
            }
    
            @Override
            public void onLowMemory() {
                super.onLowMemory();
                sMap.onLowMemory();
            }
    

    Lastly in the mainActivity.java

                getSupportFragmentManager()
                        .beginTransaction()
                        .add(R.id.map, new MapsActivity(), MapsActivity.LOG_TAG)
                        .disallowAddToBackStack()
                        .commitAllowingStateLoss();
    

    Hope this helps somebody. It was a nightmare for me. My application is now working perfectly.