How do you use a Google MapView inside of a Fragment? - java

Desired Behavior
In case it affects any answers or suggestions, what I would like to ultimately see in this app is a navigation UI where one of the Fragment is a map. I used a template to start with a navigation UI, which gave me several Fragments, one for each tab. Currently, I'm trying to use the Google API to put a MapView inside of one of those Fragments.
Current Behavior
Generally, this isn't working (obviously). I have everything up and running, but when I pull up the tab with the map in it, the map is blank. Nothing crashes, and no errors are output:
I/Google Maps Android API: Google Play services client version: 12451000
I/Google Maps Android API: Google Play services package version: 201516040
D/NetworkSecurityConfig: No Network Security Config specified, using platform default
I/DynamiteModule: Considering local module com.google.android.gms.googlecertificates:0 and remote module com.google.android.gms.googlecertificates:4
Selected remote version of com.google.android.gms.googlecertificates, version >= 4
Other Details/Troubleshooting
I know that my API key works because I have another app in which the maps work as expected. I doubt I need a second API key, but I looked anyway and since it wasn't overly obvious how to create a second API key, I think that hunch is correct. Most of the code I'm using was copied from that working example; the primary difference is that other example uses a SupportMapFragment and it has a class MapsActivity extends FragmentActivity. Here, since I don't really want to deal with nesting fragments, I'm trying to use a MapView inside that fragment.
What bits of my code that weren't copied largely came from online sources. None of what I've tried has produced better results.
MapView inside fragment is answering a different problem
Creating google maps v2 in a fragment using Navigation Drawer contains only things I already have
android MapView in Fragment gave me the idea to use MapsInitializer.initialize(this.getActivity()); but that didn't help
Mapview in separate fragment android has nothing new
how do i solve integrating google map in fragment in android uses a Fragment not a MapView
I also know that onMapReady is being called. Inside of that, I move the camera and that subsequently calls onCameraMoveStarted. However, it does not enter onCameraMove or onCameraIdle.
Code
This is the code as it is, which uses some of the ideas from the above links. I tried to cut out anything that's unrelated (other fragments, etc)
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.retailtherapy">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<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"
android:windowSoftInputMode="stateVisible|adjustResize" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-feature
android:glEsVersion="0x00020000"
android:required="true"/>
</manifest>
activity_main.xml
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/nav_view"
app:menu="#menu/bottom_nav_menu" />
<fragment
android:id="#+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
app:navGraph="#navigation/mobile_navigation"
tools:context=".MainActivity" />
</androidx.constraintlayout.widget.ConstraintLayout>
mobile_navigation.xml
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/mobile_navigation"
app:startDestination="#+id/navigation_map">
<fragment
android:id="#+id/navigation_map"
android:name="com.example.retailtherapy.ui.map.MapFragment"
android:label="Map"
tools:layout="#layout/fragment_map" />
</navigation>
fragment_map.xml
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.map.MapFragment" >
<com.google.android.gms.maps.MapView
android:id="#+id/googleMap"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
MapFragment.java
public class MapFragment extends Fragment implements OnMapReadyCallback, LocationListener {
/*************************************************************************
*
* M E M B E R S
*
*/
// Location manager for getting current location
private LocationManager mLocationManager = null;
// The map object for reference for ease of adding points and such
private GoogleMap mGoogleMap = null;
private MapView mMapView = null;
// The camera position tells us where the view is on the map
private CameraPosition mCameraPosition = Constants.GetDefaultCameraPosition();
// Current latitude and longitude of user
private LatLng mCurrentLatLong = new LatLng(0.0, 0.0);
// These are flags that allow us to selectively not move the camera and
// instead wait for idle so the movement isn't jerky
private boolean mCameraMoving = false;
private boolean mPendingUpdate = false;
/*************************************************************************
*
* F R A G M E N T
*
*/
public View onCreateView(#NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View root = inflater.inflate(R.layout.fragment_map, container, false);
// The google map...
MapsInitializer.initialize(getActivity());
mMapView = (MapView) root.findViewById(R.id.googleMap);
mMapView.onCreate(savedInstanceState);
mMapView.getMapAsync(this);
// The required permissions...
final String[] REQUIRED_PERMISSIONS = {
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.INTERNET,
};
requestPermissions(REQUIRED_PERMISSIONS, 0);
return root;
}
#Override
public void onStart() throws SecurityException {
super.onStart();
// Location manager for getting info current location
mLocationManager = (LocationManager) getActivity().getSystemService(Context.LOCATION_SERVICE);
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 0, this);
}
#Override
public void onResume() {
super.onResume();
mMapView.onResume();
}
#Override
public void onStop() {
super.onStop();
}
#Override
public void onDestroyView() {
super.onDestroyView();
mMapView.onDestroy();
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mMapView.onSaveInstanceState(outState);
}
#Override
public void onLowMemory() {
super.onLowMemory();
mMapView.onLowMemory();
}
/*************************************************************************
*
* G O O G L E M A P S
*
*/
#Override
public void onMapReady(GoogleMap googleMap) {
mGoogleMap = googleMap;
// Assuming if here that we have permissions okay. Dangerous sure
// but I'm lazy...
mGoogleMap.setMyLocationEnabled(true);
// Store the location of the camera for UI purposes so we can go
// back to where we were
mGoogleMap.setOnCameraMoveListener(new GoogleMap.OnCameraMoveListener() {
#Override
public void onCameraMove() {
mCameraPosition = mGoogleMap.getCameraPosition();
}
});
mGoogleMap.setOnCameraMoveStartedListener(new GoogleMap.OnCameraMoveStartedListener() {
#Override
public void onCameraMoveStarted(int i) {
mCameraMoving = true;
}
});
mGoogleMap.setOnCameraIdleListener(new GoogleMap.OnCameraIdleListener() {
#Override
public void onCameraIdle() {
mCameraMoving = false;
if (mPendingUpdate) {
centerCameraOnLocation(false);
}
}
});
// Start with user centered in view
centerCameraOnLocation(true);
}
/*************************************************************************
*
* L O C A T I O N
*
*/
#Override
public void onLocationChanged(Location location) {
// Store the current location so if we start auto-center but aren't
// already, we know where to pan to
LatLng newLatLng = new LatLng(location.getLatitude(), location.getLongitude());
if (!newLatLng.equals(mCurrentLatLong)) {
mCurrentLatLong = new LatLng(location.getLatitude(), location.getLongitude());
centerCameraOnLocation(false);
}
}
#Override
public void onStatusChanged(String provider, int status, Bundle extras) {}
#Override
public void onProviderDisabled(String provider) {}
#Override
public void onProviderEnabled(String provider) {}
/*************************************************************************
*
* M A P C A M E R A
*
*/
/*
* Updates the camera position based on current state. If the camera is
* already moving, then wait.
*/
private void centerCameraOnLocation(boolean animate) {
if (mGoogleMap == null) {
return;
}
if (mCameraMoving) {
mPendingUpdate = true;
return;
}
// Get the CameraPosition to update to based on whether we are auto
// centering or not.
if (mCameraPosition == null) {
mCameraPosition = Constants.GetDefaultCameraPosition();
}
// If this is the same as it was before, then don't reset it
if (mCameraPosition.equals(mGoogleMap.getCameraPosition())) {
return;
}
// Make the update so we can move the camera
CameraUpdate cameraUpdate = CameraUpdateFactory.newCameraPosition(mCameraPosition);
// Move the camera with or without animation. The time we don't want
// animation is when we're setting the position initially
if (animate) {
mGoogleMap.animateCamera(cameraUpdate);
}
else {
mGoogleMap.moveCamera(cameraUpdate);
}
mPendingUpdate = false;
}
}

FINALLY I found the answer in a comment at https://stackoverflow.com/a/51464293/3038157.
Basically, it seems there was something stale. Without changing any code, I uninstalled the app, cleaned the build, rebuilt, and voila it worked.

You must forward all the life cycle methods from the Activity or Fragment containing this view to the corresponding ones in this class.
View this official guide
or example code
// *** IMPORTANT ***
// MapView requires that the Bundle you pass contain _ONLY_ MapView SDK
// objects or sub-Bundles.

Related

Google Map is not showing in Android Studio (Only Google Icon is Visible)

I want to import Google Map SDK in my project. According to Docs I follow all the steps perfectly but when i run the project it only show an icon of google instead of Google Map.
I search a lot but nothing find to solve my problem
Here is my Main Activity named as GoogleMapLocat:
public class GoogleMapLocat extends AppCompatActivity {
//map data
private static final String TAG = "HomeActivity";
private static final int ERROR_DIALOG_REQUEST = 9001;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_google_map_locat);
//map data
if (isServicesOK()) {
init();
}
}
//MAP DATA
public void init() {
Button btnMap = (Button) findViewById(R.id.btnMap);
btnMap.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent intent = new Intent(GoogleMapLocat.this, MapActivity.class);
startActivity(intent);
}
});
}
public boolean isServicesOK() {
Log.d(TAG, "isServicesOK: checking google services version");
int available = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(GoogleMapLocat.this);
if (available == ConnectionResult.SUCCESS) {
Log.d(TAG, "Google Play Services is Working");
return true;
} else if (GoogleApiAvailability.getInstance().isUserResolvableError(available)) {
//an error occured but we can resolve it
Log.d(TAG, "isServicesOK: an error occured but we can fix it");
Dialog dialog = GoogleApiAvailability.getInstance().getErrorDialog(GoogleMapLocat.this, available, ERROR_DIALOG_REQUEST);
dialog.show();
} else {
Toast.makeText(this, "you can,t make map request", Toast.LENGTH_LONG).show();
}
return false;
}
}
Here is my Second Activity named as MapActivity:
public class MapActivity extends FragmentActivity implements OnMapReadyCallback{
private static final String TAG = "MapActivity";
private static final String FINE_LOCATION = Manifest.permission.ACCESS_FINE_LOCATION;
private static final String COURSE_LOCATION = Manifest.permission.ACCESS_COARSE_LOCATION;
private static final int LOCATION_PERMISSION_REQUEST_CODE = 1234;
//vars
private Boolean mLocationPermissionsGranted = false;
private GoogleMap mMap;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_map);
getLocationPermission();
}
#Override
public void onMapReady(GoogleMap googleMap) {
Toast.makeText(this, "Map is Ready", Toast.LENGTH_SHORT).show();
Log.d(TAG, "onMapReady: map is ready");
mMap = googleMap;
}
private void initMap(){
Log.d(TAG, "initMap: initializing map");
SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map);
mapFragment.getMapAsync(MapActivity.this);
}
private void getLocationPermission(){
Log.d(TAG, "getLocationPermission: getting location permissions");
String[] permissions = {Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION};
if(ContextCompat.checkSelfPermission(this.getApplicationContext(),
FINE_LOCATION) == PackageManager.PERMISSION_GRANTED){
if(ContextCompat.checkSelfPermission(this.getApplicationContext(),
COURSE_LOCATION) == PackageManager.PERMISSION_GRANTED){
mLocationPermissionsGranted = true;
}else{
ActivityCompat.requestPermissions(this,
permissions,
LOCATION_PERMISSION_REQUEST_CODE);
}
}else{
ActivityCompat.requestPermissions(this,
permissions,
LOCATION_PERMISSION_REQUEST_CODE);
}
}
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
Log.d(TAG, "onRequestPermissionsResult: called.");
mLocationPermissionsGranted = false;
switch(requestCode){
case LOCATION_PERMISSION_REQUEST_CODE:{
if(grantResults.length > 0){
for(int i = 0; i < grantResults.length; i++){
if(grantResults[i] != PackageManager.PERMISSION_GRANTED){
mLocationPermissionsGranted = false;
Log.d(TAG, "onRequestPermissionsResult: permission failed");
return;
}
}
Log.d(TAG, "onRequestPermissionsResult: permission granted");
mLocationPermissionsGranted = true;
//initialize our map
initMap();
}
}
}
}
}
GoogleMapLocat.xml is here:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".GoogleMapLocat">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Map"
android:id="#+id/btnMap"
tools:ignore="MissingConstraints" />
MapActivity.xml is here:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/map"
tools:context=".MapsActivity"
android:name="com.google.android.gms.maps.SupportMapFragment" />
What,s wrong in this code?It only shows google icon in left corner at the bottom of screen instead of map.
I can't see if you have configured the google map key. You have to define it in the manifest. You can define with this line:
<meta-data android:name="com.google.android.geo.API_KEY" android:value="YOUR_KEY_GOES_HERE" />
You can review this link to get your key: https://developers.google.com/maps/documentation/android-sdk/get-api-key
I suggest you must define 2 APIs for release & debug in APIs & credentials.
For example, use debug & release sign key in both APIs.
I had the same problem a few days ago.
Try to remove "Application restrictions" on Google API Console.
It worked for me.
I was testing on device and it was not connected to internet...
I connected to WiFi and map started showing, if was silently failing.
If someone end up on this question, don't forget to check your connection.
I encountered this problem too and I had ensured that:
The project SDK was setup correctly
Google Console was setup correctly with no key restriction
I confirmed the above by using the same key in other android project and the map could load successfully. Then I created another new android project with the same package name with my original one and surprising this time the map could not load again. I concluded it was the package name that made the map loading failed. I am not sure why but I solved this by refactoring my package name to another one.

Using the camera in android

I am trying to build an Android app that simply uses the camera to take a picture without launching the default camera app. In other words, I want to make a custom camera app. I can do this using the Camera hardware object class, however this is deprecated and I want to use some of the new features of camerax and not have to worry about the code not working after some time. I have also read the camera API documentation, however it is still unclear how to use the camera. Are there any very simple step by step tutorials or guides that might help me?
Thanks,
You can check my example about how to use AndroidX libraries and TextureView for camera customization.
https://github.com/icerrate/Custom-Camera-App
First at all, define your layout. This is my activity_main.xml file:
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextureView
android:id="#+id/view_finder"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/take_photo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_margin="#dimen/horizontal_margin"
app:layout_constraintStart_toStartOf="parent"
android:src="#drawable/ic_camera"/>
</androidx.constraintlayout.widget.ConstraintLayout>
Remember that TextureView will receive the camera preview and the Floating Action Button works as a "Take Photo" button.
Then add your MainActivity.java file:
public class MainActivity extends AppCompatActivity implements LifecycleOwner {
private static final int RC_PERMISSIONS = 100;
private TextureView viewFinder;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_main);
viewFinder = findViewById(R.id.view_finder);
FloatingActionButton takePhotoFab = findViewById(R.id.take_photo);
//Check permissions
if (allPermissionGranted()) {
viewFinder.post(new Runnable() {
#Override
public void run() {
startCamera();
}
});
} else {
ActivityCompat.requestPermissions(this,
new String[] {Manifest.permission.CAMERA}, RC_PERMISSIONS);
}
takePhotoFab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
takePhoto();
}
});
}
private void startCamera() {
Point screenSize = getScreenSize();
int width = screenSize.x;
int height = screenSize.y;
//Get real aspect ratio
DisplayMetrics displayMetrics = new DisplayMetrics();
Display display = getWindowManager().getDefaultDisplay();
display.getRealMetrics(displayMetrics);
Rational rational = new Rational(displayMetrics.widthPixels, displayMetrics.heightPixels);
//Build the camera preview
PreviewConfig build = new PreviewConfig.Builder()
.setTargetAspectRatio(rational)
.setTargetResolution(new Size(width,height))
.build();
Preview preview = new Preview(build);
preview.setOnPreviewOutputUpdateListener(new Preview.OnPreviewOutputUpdateListener() {
#Override
public void onUpdated(Preview.PreviewOutput output) {
ViewGroup group = (ViewGroup) viewFinder.getParent();
group.removeView(viewFinder);
group.addView(viewFinder, 0);
viewFinder.setSurfaceTexture(output.getSurfaceTexture());
}
});
CameraX.bindToLifecycle(this, preview);
}
private void takePhoto() {
Toast.makeText(this, "Shot!",
Toast.LENGTH_SHORT).show();
}
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == RC_PERMISSIONS) {
if (allPermissionGranted()) {
viewFinder.post(new Runnable() {
#Override
public void run() {
startCamera();
}
});
} else {
Toast.makeText(this, "Permission not granted",
Toast.LENGTH_SHORT).show();
finish();
}
}
}
private boolean allPermissionGranted() {
return ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED;
}
private Point getScreenSize() {
Display display = getWindowManager(). getDefaultDisplay();
Point size = new Point();
display.getSize(size);
return size;
}
}
In this class, you would be able to send the camera preview to the TextureView, with help of PreviewConfig.Builder() and binding it to the Activity lifeCycle using CameraX.bindToLifeCycle()
Also, don't forget to add Camera permission to the manifest and consider runtime permissions.
Screenshot:
Custom Camera preview
Hope this help you!

My layout values are not updating

So I have Track.java with its layout that is just suppose to show me updated GPS coordinates with myTextLat and myTextLong. And a MainActivity.java that has a method locationChanged that spits out new GPS data as it becomes available, but for whatever reason my layout is not updating with the new data, despite being able to see new coordinate data coming out of locationChanged in the system out. I can statically set them by doing a settext in onCreateView, but for some reason they will not update through setMyCoords. Can someone help me figure out why the data, when available, is not being passed into my layout? Is there another, better, way to pass data from the activity to objects in a fragment so they are always updatedy? Thanks.
MainActivity's locationChanged
#Override
public void locationChanged(double longitude, double latitude) {
try {
System.out.println("Longitude: " +longitude);
System.out.println("Latitude: " + latitude);
Track f = new Track();
f.setMyCoords(latitude,longitude);
} catch (NullPointerException e) {
}
Track.java
package "";
import android.app.Activity;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
public class Track extends Fragment {
MiddleMan mCallBack;
TextView myTextLat;
TextView myTextLong;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mCallBack = (MiddleMan) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement ReqestConnect");
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.track_display, container, false);
myTextLat = (TextView) view.findViewById(R.id.textView_lat);
myTextLong = (TextView) view.findViewById(R.id.textView_long);
mCallBack.DisplayHome();
return view;
}
public void setMyCoords(final double slat, final double slong) {
myTextLat.setText(Double.toString(slat));
myTextLong.setText(Double.toString(slong));
}
}
This might also help. Each fragment replaces a framelayout in MainActivity when called. it looks like this.
#Override
public void ShiftView(Object obj) {
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.activity_main_framelayout, (Fragment) obj);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.addToBackStack(null);
ft.commit();
}
With the actual call being something like.
Track f = new Track();
ShiftView(f);
Conclusion
With Joel Min's help I was able to come to the conclusion to my problem. I only have one activity but use several fragments to take on the role of having multiple activities, from the viewpoint of the user:
activity_main.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" android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
android:paddingBottom="#dimen/activity_vertical_margin"
android:orientation="vertical" tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#27b"
android:layout_weight=".04">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/activity_main_framelayout">
</FrameLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight=".9"
android:orientation="horizontal">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#1B5F96"
android:layout_weight=".9"
android:id="#+id/activity_main_status_title"
android:text="#string/activity_main_status_title"
tools:ignore="NestedWeights" />
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#AD3333"
android:layout_weight=".15"
android:id="#+id/activity_main_status_value"
android:text="#string/activity_main_status_value"/>
</LinearLayout>
</LinearLayout>
The Framelayout eats the vast majority of the display, with ShiftView basically swapping in the fragment's layout by calling on the fragment class, as stated above. The problem? Shifting views is done by the onOptionsitemSelected method where each entry essentially looks like this:
if (id == R.id.action_track_display) {
Track f = new Track();
ShiftView(f);
return true;
Which has been fine for the project up to this point, however, Track.java needs to do something the other classes don't, it needs to receive and retain gps data regardless of where the user is in the app. My menu produces a new Track object, my locationChanged method produces a new Track object every time the location changes [which is a lot of objects], none of the objects are the same and none are connected to MainActivity.java in any way. The result, you get a crashless app that has a Track object's layout visible to the user that never updates and a series of background Track objects that exist for a fraction of a second, each containing one set of gps points. The fix, pretty simple:
MainActivity.java
public class MainActivity extends AppCompatActivity {
Track my_track;
...
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_track_display) {
if (my_track==null) {
my_track = new Track();
}
ShiftView((my_track);
return true;
}
....
#Override
public void locationChanged(final double longitude, final double latitude) {
try {
System.out.println("Main-Longitude: " +longitude);
System.out.println("Main-Latitude: " + latitude);
runOnUiThread(new Runnable() {
#Override
public void run() {
my_track.setMyCoords(latitude,longitude);
}
});
} catch (NullPointerException e) {}
Each fragment replaces a framelayout in MainActivity when called
But you are calling f.setMyCoords(latitude,longitude); after the fragment has been created and returned to the main UI as it is (without setMyCoords applied). So move f.setMyCoords(latitude,longitude); from your locationChanged method to ShitView method. Of course then you will need to have global variables tempLong and tempLat to temporarily store the longitude and latitude values in locationChanged, and access them in ShiftView. Below is the modified code:
private double tempLong, tempLat; //declare it at class level
#Override
public void locationChanged(double longitude, double latitude) {
try {
System.out.println("Longitude: " +longitude);
System.out.println("Latitude: " + latitude);
Track f = new Track();
tempLong = longitude;
tempLat = latitude;
} catch (NullPointerException e) {
}
#Override
public void ShiftView(Object obj) {
(Fragment) obj.setMyCoords(tempLat, tempLong);
//if above line causes error try the line below;
//Track f = (Fragment) obj;
//f.setMyCoords(tempLat, tempLong);
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.activity_main_framelayout, (Fragment) obj);
//ft.replace(R.id.activity_main_framelayout, f);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.addToBackStack(null);
ft.commit();
}
I cannot guarantee the above code would work because I don't have the full code. But basically you either need to set the longitude and latitude before the fragment transition in main activity occurs, or set a callback mechanism in your setMyCoords method so when it's called it calls back the main activity to update the textviews with new long and lat.
It seems that you are calling setText from NOT ui thread.
Consider calling it in UI thread using smth like this:
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
myTextLat.setText(Double.toString(slat));
myTextLong.setText(Double.toString(slong));
}
});

Google Analytics v4 cannot be cast to Analytics

I try to implement the Google Analytics in my App.
The Problem is I get in LogCat the message:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.name.appname/com.name.appname.activity.MainActivity}: java.lang.ClassCastException: com.name.appname.misc.AppApplication cannot be cast to com.name.appname.activity.Analytics
global_tracker.xml
<integer name="ga_sessionTimeout">300</integer>
<!-- Enable automatic Activity measurement -->
<bool name="ga_autoActivityTracking">true</bool>
<!-- Enable verbose logging -->
<String name="ga_loglevel">verbose</String>
<!-- Screen names on the reports -->
<screenName name="com.name.appname.activity.MainActivity">
MainActivity ScreenView
</screenName>
<!-- Tracking ID -->
<string name="ga_trackingId">UA-xxxxxxx-2</string>
ANALytics.java:
public class Analytics extends Application {
public static int GENERAL_TRACKER = 0;
public enum TrackerName {
APP_TRACKER, // Tracker used only in this app.
GLOBAL_TRACKER, // Tracker used by all the apps from a company. eg: roll-up tracking.
ECOMMERCE_TRACKER, // Tracker used by all ecommerce transactions from a company.
}
HashMap<TrackerName, Tracker> mTrackers = new HashMap<TrackerName, Tracker>();
synchronized Tracker getTracker(TrackerName trackerId) {
if (!mTrackers.containsKey(trackerId)) {
GoogleAnalytics analytics = GoogleAnalytics.getInstance(this);
Tracker t = analytics.newTracker(R.xml.global_tracker);
mTrackers.put(trackerId, t);
}
return mTrackers.get(trackerId);
}
}
manifest:
<application
android:allowBackup="true"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name"
android:theme="#style/AppTheme"
android:name=".misc.AppApplication" >
<activity
android:name=".activity.Analytics"
android:allowBackup="true"
android:label="#string/app_name"
android:screenOrientation="portrait" >
</activity>
MainActivity:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
adView = (AdView) this.findViewById(R.id.adView);
// Get tracker.
Tracker tracker = ((Analytics)getApplication()).getTracker(Analytics.TrackerName.GLOBAL_TRACKER);
tracker.send(new HitBuilders.EventBuilder()
.setCategory("Use")
.setAction("Use Programm")
.setLabel("submit")
.build());
#Override
protected void onStart() {
// TODO Auto-generated method stub
super.onStart();
GoogleAnalytics.getInstance(MainActivity.this).reportActivityStart(this);
}
#Override
protected void onStop() {
// TODO Auto-generated method stub
super.onStop();
GoogleAnalytics.getInstance(MainActivity.this).reportActivityStop(this);
}
In my manifest i have already declared android:name=".misc.AppApplication" at application. there is my check for PremiumUsers.
Here is my misc.AppApplication:
import android.app.Application;
public class AppApplication extends Application {
private boolean mIsPremium;
public void setPremium(){
mIsPremium = true;
}
public boolean isPremium(){
return mIsPremium;
}
}
where is the problem?
You are setting the name attribute of your application element in the manifest to ".misc.AppApplication". That instructs Android to instantiate .misc.AppApplication class as application instead of the default Application. In your MainActivity class you try to cast .misc.AppApplication to Analytics and that will throw ClassCastException.
The correct setup is to replace the Application class with Analytics (the class that extends Android Application class) and keep MainActivity as the class implementing your main activity.
You should also not be logging events from onCreate override. Activity can be created for reasons other then starting the app. For example when the device changes from landscape to portrait mode Android will ask the activity to save its state to a bundle, tear it down and recreate it passing the saved state in savedInstanceState. onCreate will be called in this case again.

Multiple Clickable links in TextView on Android

I'm trying to add multiple links in a textview similar to what Google & Flipboard has done below with their Terms and conditions AND Privacy Policy shown in the screen shot below:
So far I've stumbled on using this approach
textView.setText(Html.fromHtml(myHtml);
textView.setMovementMethod(LinkMovementMethod.getInstance());
where myHtml is a href.
But it doesn't give me control I need e.g to launch a fragment etc.
Any idea how they achieve this in the two examples below?
I think that I'm a little late to share this, but I have achieved the same using SpannableStringBuilder.
Simply initialize the TextView that you want to add 2 or more listeners and then pass that to the following method that I have created:
private void customTextView(TextView view) {
SpannableStringBuilder spanTxt = new SpannableStringBuilder(
"I agree to the ");
spanTxt.append("Term of services");
spanTxt.setSpan(new ClickableSpan() {
#Override
public void onClick(View widget) {
Toast.makeText(getApplicationContext(), "Terms of services Clicked",
Toast.LENGTH_SHORT).show();
}
}, spanTxt.length() - "Term of services".length(), spanTxt.length(), 0);
spanTxt.append(" and");
spanTxt.setSpan(new ForegroundColorSpan(Color.BLACK), 32, spanTxt.length(), 0);
spanTxt.append(" Privacy Policy");
spanTxt.setSpan(new ClickableSpan() {
#Override
public void onClick(View widget) {
Toast.makeText(getApplicationContext(), "Privacy Policy Clicked",
Toast.LENGTH_SHORT).show();
}
}, spanTxt.length() - " Privacy Policy".length(), spanTxt.length(), 0);
view.setMovementMethod(LinkMovementMethod.getInstance());
view.setText(spanTxt, BufferType.SPANNABLE);
}
And in your XML, use android:textColorLink to add custom link color of your choice. Like this:
<TextView
android:id="#+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView"
android:textColorLink="#C36241" /> //#C36241 - Rust
And this looks like this:
You can use Linkify (android.text.Spannable,java.util.regex.Pattern,java.lang.String)
String termsAndConditions = getResources().getString(R.string.terms_and_conditions);
String privacyPolicy = getResources().getString(R.string.privacy_policy);
legalDescription.setText(
String.format(
getResources().getString(R.string.message),
termsAndConditions,
privacyPolicy)
);
legalDescription.setMovementMethod(LinkMovementMethod.getInstance());
Pattern termsAndConditionsMatcher = Pattern.compile(termsAndConditions);
Linkify.addLinks(legalDescription, termsAndConditionsMatcher, "terms:");
Pattern privacyPolicyMatcher = Pattern.compile(privacyPolicy);
Linkify.addLinks(legalDescription, privacyPolicyMatcher, "privacy:");
and then you can use the scheme to start an activity for example by adding the scheme in the AndroidManifest:
<intent-filter>
<category android:name="android.intent.category.DEFAULT" />
<action android:name="android.intent.action.VIEW" />
<data android:scheme="terms" />
<data android:scheme="privacy" />
</intent-filter>
If you want to do a custom action, you can set the intent-filter to your current activity, which will have a singleTop launchmode.
This will cause onNewIntent to be fired where can make your custom actions:
#Override
protected void onNewIntent(final Intent intent) {
...
if (intent.getScheme().equals(..)) {
..
}
}
Here's my solution:
First we need to have clickable links in our TextView:
Here's my TextView in the xml layout, do not add any links handling
parameters.
<TextView
android:id="#+id/sign_up_privacy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/terms_and_privacy"/>
In strings file, I added the resource text with html tags
<string name="terms_and_privacy">By signing up you agree to our Terms of Service and Privacy Policy.</string>
In onCreateView set LinkMovementMethod to the TextView
TextView privacyTextView = (TextView) root.findViewById(R.id.sign_up_privacy);
privacyTextView.setMovementMethod(LinkMovementMethod.getInstance());
Now the TextView links are clickable.
Second, We need to Handle these clicks:
In my Manifest file, I added intent-filter for "terms" and "privacy"
and Single Instance Launch Mode
<activity
android:name=".MyActivity"
android:launchMode="singleInstance">
<intent-filter>
<category android:name="android.intent.category.DEFAULT"/>
<action android:name="android.intent.action.VIEW"/>
<data android:scheme="terms"/>
<data android:scheme="privacy"/>
</intent-filter>
</activity>
In MyActivity, override onNewIntent to catch privacy and terms
intents
#Override
protected void onNewIntent(Intent intent)
{
if (intent.getScheme().equalsIgnoreCase(getString("terms")))
{
//handle terms clicked
}
else if (intent.getScheme().equalsIgnoreCase(getString("privacy")))
{
//handle privacy clicked
}
else
{
super.onNewIntent(intent);
}
}
The best way to do this is with string file
In string.xml
<string name="agree_to_terms">I agree to the %1$s and %2$s</string>
<string name="terms_of_service">Terms of Service</string>
<string name="privacy_policy">Privacy Policy</string>
In onCreateView call below method
private void setTermsAndCondition() {
String termsOfServicee = getString(R.string.terms_of_service);
String privacyPolicy = getString(R.string.privacy_policy);
String completeString = getString(R.string.agree_to_terms, termsOfServicee,privacyPolicy);
int startIndex = completeString.indexOf(termsOfServicee);
int endIndex = startIndex + termsOfServicee.length();
int startIndex2 = completeString.indexOf(privacyPolicy);
int endIndex2 = startIndex2 + privacyPolicy.length();
SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(completeString);
ClickableSpan clickOnTerms = new ClickableSpan() {
#Override
public void onClick(View widget) {
Toast.makeText(this, "click on terms of service", Toast.LENGTH_SHORT).show();
}
#Override
public void updateDrawState(TextPaint ds) {
ds.setColor(ContextCompat.getColor(this, R.color.blue));
}
};
ClickableSpan clickOnPrivacy = new ClickableSpan() {
#Override
public void onClick(View widget) {
Toast.makeText(this, "click on privacy policy", Toast.LENGTH_SHORT).show();
}
#Override
public void updateDrawState(TextPaint ds) {
ds.setColor(ContextCompat.getColor(this, R.color.blue));
}
};
spannableStringBuilder.setSpan(clickOnTerms, startIndex, endIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
spannableStringBuilder.setSpan(clickOnPrivacy, startIndex2, endIndex2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
yourTextView.setText(spannableStringBuilder);
yourTextView.setMovementMethod(LinkMovementMethod.getInstance());
}
With Textoo, this can be achieved like:
res/values/strings.xml:
<resources>
<string name="str_terms_and_privacy">By signing up you agree to our Terms of Service and Privacy Policy.</string>
</resources>
res/layout/myActivity.xml:
<TextView
android:id="#+id/view_terms_and_privacy"
android:text="#string/str_terms_and_privacy"
/>
java/myPackage/MyActivity.java:
public class MyActivity extends Activity {
...
protected void onCreate(Bundle savedInstanceState) {
...
TextView termsAndPrivacy = Textoo
.config((TextView) findViewById(R.id.view_terms_and_privacy))
.addLinksHandler(new LinksHandler() {
#Override
public boolean onClick(View view, String url) {
if ("terms:".equals(url)) {
// Handle terms click
return true; // event handled
} else if ("privacy:".equals(url)) {
// Handle privacy click
return true; // event handled
} else {
return false; // event not handled. continue default processing i.e. launch web browser and display the link
}
}
})
.apply();
...
}
...
}
This approach have the advantages that:
Keep text externalized as string resource. Make it easier to localize your app.
Can handle the click event directly using LinksHandler and no need to define additional intent filter
Simpler and more readable code
This is working for me :
in xml :
<TextView
android:id="#+id/tv_by_continuing_str"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:textSize="15sp"
tools:text="Test msg 1 2, 3"
android:textColor="#color/translucent_less_white3"
android:textColorLink="#color/white"
android:gravity="center|bottom"
android:layout_above="#+id/btn_privacy_continue" />
In strings.xml
< string name="by_continuing_str2">< ! [ CDATA[By continuing to use this app, you agree to our <a href="https://go.test.com" style="color:gray"/> Privacy Statement </a> and <a href="https://go.test.com" style="color:gray"/>Services Agreement.]]>< / string>
in the activity :
TextView tv_by_continuing = (TextView) findViewById(R.id.tv_by_continuing);
tv_by_continuing.setText(Html.fromHtml(getString(R.string.by_continuing_str2)));
tv_by_continuing.setMovementMethod(LinkMovementMethod.getInstance());
private fun customTextView(view: TextView) {
//I agree to the Terms of service & Privacy Policy,
val spanTxt = SpannableStringBuilder(
"I agree to the ")
spanTxt.append("Terms of service")
spanTxt.setSpan(object : ClickableSpan() {
override fun onClick(widget: View) {
toast("Terms of service link clicked")
}
}, spanTxt.length - "Term of services".length, spanTxt.length, 0)
spanTxt.append(" and")
spanTxt.append(" Privacy Policy")
spanTxt.setSpan(object : ClickableSpan() {
override fun onClick(widget: View) {
toast("Privacy Policy link clicked")
}
}, spanTxt.length - " Privacy Policy".length, spanTxt.length, 0)
view.movementMethod = LinkMovementMethod.getInstance()
view.setText(spanTxt, TextView.BufferType.SPANNABLE)
}
An easy fix for this would be to use multiple labels. One for each link and one for the text.
[TermsOfServiceLabel][andLabel][PrivacyPolicy]
Or is it necessary that you only use one label?

Categories