Skip to content

Commit 7855636

Browse files
Shahroz16claude
andcommitted
feat: integrate location module into sample apps
Register ModuleLocation in both sample apps. Add location testing UI to the Java sample app: request location permission, set manual location, and request one-shot SDK-managed location. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 42f29b8 commit 7855636

File tree

12 files changed

+637
-1
lines changed

12 files changed

+637
-1
lines changed

samples/java_layout/src/main/AndroidManifest.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
xmlns:tools="http://schemas.android.com/tools">
44

55
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
6+
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
67

78
<!-- network security config set to allow proxy tools to view HTTP communication for app debugging.
89
tools:replace is needed because SDK declares a network security config as well for automated tests.
@@ -126,6 +127,10 @@
126127
android:name=".ui.settings.InternalSettingsActivity"
127128
android:exported="false"
128129
android:label="@string/label_internal_settings_activity" />
130+
<activity
131+
android:name=".ui.location.LocationTestActivity"
132+
android:exported="false"
133+
android:label="@string/label_location_test_activity" />
129134
<activity
130135
android:name=".ui.inline.InlineExamplesActivity"
131136
android:exported="false"

samples/java_layout/src/main/java/io/customer/android/sample/java_layout/sdk/CustomerIORepository.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import io.customer.android.sample.java_layout.support.Optional;
1414
import io.customer.messaginginapp.MessagingInAppModuleConfig;
1515
import io.customer.messaginginapp.ModuleMessagingInApp;
16+
import io.customer.location.ModuleLocation;
1617
import io.customer.messagingpush.ModuleMessagingPushFCM;
1718
import io.customer.sdk.CustomerIO;
1819
import io.customer.sdk.CustomerIOConfig;
@@ -38,6 +39,9 @@ public void initializeSdk(SampleApplication application) {
3839
// Enables push notification
3940
builder.addCustomerIOModule(new ModuleMessagingPushFCM());
4041

42+
// Enables location tracking
43+
builder.addCustomerIOModule(new ModuleLocation());
44+
4145
// Enables in-app messages
4246
if (sdkConfig.isInAppMessagingEnabled()) {
4347
builder.addCustomerIOModule(new ModuleMessagingInApp(

samples/java_layout/src/main/java/io/customer/android/sample/java_layout/ui/dashboard/DashboardActivity.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import io.customer.android.sample.java_layout.sdk.CustomerIORepository;
3434
import io.customer.android.sample.java_layout.ui.core.BaseActivity;
3535
import io.customer.android.sample.java_layout.ui.inline.InlineExamplesActivity;
36+
import io.customer.android.sample.java_layout.ui.location.LocationTestActivity;
3637
import io.customer.android.sample.java_layout.ui.login.LoginActivity;
3738
import io.customer.android.sample.java_layout.ui.settings.InternalSettingsActivity;
3839
import io.customer.android.sample.java_layout.ui.settings.SettingsActivity;
@@ -149,6 +150,9 @@ private void setupViews() {
149150
binding.showPushPromptButton.setOnClickListener(view -> {
150151
requestNotificationPermission();
151152
});
153+
binding.locationTestButton.setOnClickListener(view -> {
154+
startActivity(new Intent(DashboardActivity.this, LocationTestActivity.class));
155+
});
152156
binding.inlineExamplesButton.setOnClickListener(view -> {
153157
startInlineExamplesActivity();
154158
});
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
package io.customer.android.sample.java_layout.ui.location;
2+
3+
import android.Manifest;
4+
import android.content.Context;
5+
import android.content.Intent;
6+
import android.content.pm.PackageManager;
7+
import android.location.Location;
8+
import android.location.LocationListener;
9+
import android.location.LocationManager;
10+
import android.net.Uri;
11+
import android.provider.Settings;
12+
import android.view.MenuItem;
13+
14+
import androidx.activity.result.ActivityResultLauncher;
15+
import androidx.activity.result.contract.ActivityResultContracts;
16+
import androidx.annotation.NonNull;
17+
import androidx.core.content.ContextCompat;
18+
19+
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
20+
import com.google.android.material.snackbar.Snackbar;
21+
22+
import io.customer.android.sample.java_layout.R;
23+
import io.customer.android.sample.java_layout.databinding.ActivityLocationTestBinding;
24+
import io.customer.android.sample.java_layout.ui.core.BaseActivity;
25+
import io.customer.location.ModuleLocation;
26+
import io.customer.sdk.CustomerIO;
27+
28+
public class LocationTestActivity extends BaseActivity<ActivityLocationTestBinding> {
29+
30+
private static final double[][] PRESET_COORDS = {
31+
{40.7128, -74.0060}, // New York
32+
{51.5074, -0.1278}, // London
33+
{35.6762, 139.6503}, // Tokyo
34+
{-33.8688, 151.2093}, // Sydney
35+
{-23.5505, -46.6333}, // Sao Paulo
36+
{0.0, 0.0} // 0, 0
37+
};
38+
private static final String[] PRESET_NAMES = {
39+
"New York", "London", "Tokyo", "Sydney", "Sao Paulo", "0, 0"
40+
};
41+
42+
private LocationManager locationManager;
43+
private LocationListener locationListener;
44+
private boolean userRequestedCurrentLocation = false;
45+
private boolean userRequestedSdkLocation = false;
46+
47+
private final ActivityResultLauncher<String[]> locationPermissionLauncher =
48+
registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), permissions -> {
49+
boolean fineGranted = Boolean.TRUE.equals(permissions.get(Manifest.permission.ACCESS_FINE_LOCATION));
50+
boolean coarseGranted = Boolean.TRUE.equals(permissions.get(Manifest.permission.ACCESS_COARSE_LOCATION));
51+
if (fineGranted || coarseGranted) {
52+
if (userRequestedCurrentLocation) {
53+
userRequestedCurrentLocation = false;
54+
fetchDeviceLocation();
55+
} else if (userRequestedSdkLocation) {
56+
userRequestedSdkLocation = false;
57+
performSdkLocationRequest();
58+
}
59+
} else {
60+
showPermissionDeniedAlert();
61+
}
62+
});
63+
64+
@Override
65+
protected ActivityLocationTestBinding inflateViewBinding() {
66+
return ActivityLocationTestBinding.inflate(getLayoutInflater());
67+
}
68+
69+
@Override
70+
protected void setupContent() {
71+
if (getSupportActionBar() != null) {
72+
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
73+
getSupportActionBar().setTitle(R.string.location_test_title);
74+
}
75+
76+
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
77+
CustomerIO.instance().screen(getString(R.string.location_test_title));
78+
79+
setupPresetButtons();
80+
setupSdkLocationButtons();
81+
setupDeviceLocationButton();
82+
setupManualEntrySection();
83+
}
84+
85+
@Override
86+
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
87+
if (item.getItemId() == android.R.id.home) {
88+
finish();
89+
return true;
90+
}
91+
return super.onOptionsItemSelected(item);
92+
}
93+
94+
private void setupPresetButtons() {
95+
int[] buttonIds = {
96+
R.id.preset_new_york, R.id.preset_london, R.id.preset_tokyo,
97+
R.id.preset_sydney, R.id.preset_sao_paulo, R.id.preset_zero
98+
};
99+
for (int i = 0; i < buttonIds.length; i++) {
100+
final int index = i;
101+
findViewById(buttonIds[i]).setOnClickListener(v ->
102+
setLocation(PRESET_COORDS[index][0], PRESET_COORDS[index][1], PRESET_NAMES[index])
103+
);
104+
}
105+
}
106+
107+
private void setupSdkLocationButtons() {
108+
binding.requestSdkLocationOnce.setOnClickListener(v -> requestSdkLocationOnce());
109+
binding.stopLocationUpdates.setOnClickListener(v -> stopSdkLocationUpdates());
110+
}
111+
112+
private void setupDeviceLocationButton() {
113+
binding.useCurrentLocation.setOnClickListener(v -> requestCurrentLocation());
114+
}
115+
116+
private void setupManualEntrySection() {
117+
binding.setManualLocation.setOnClickListener(v -> setManualLocation());
118+
}
119+
120+
// --- Location Actions ---
121+
122+
private void setLocation(double latitude, double longitude, String sourceName) {
123+
ModuleLocation.instance().getLocationServices().setLastKnownLocation(latitude, longitude);
124+
String sourceText = sourceName != null ? " (" + sourceName + ")" : "";
125+
binding.lastSetLocationLabel.setText(
126+
getString(R.string.last_set_format, latitude, longitude, sourceText)
127+
);
128+
showSnackbar(getString(R.string.location_set_success, sourceText));
129+
}
130+
131+
private void setManualLocation() {
132+
String latText = binding.latitudeInput.getText() != null
133+
? binding.latitudeInput.getText().toString().trim() : "";
134+
String lonText = binding.longitudeInput.getText() != null
135+
? binding.longitudeInput.getText().toString().trim() : "";
136+
137+
if (latText.isEmpty() || lonText.isEmpty()) {
138+
showSnackbar(getString(R.string.enter_valid_coordinates));
139+
return;
140+
}
141+
142+
try {
143+
double latitude = Double.parseDouble(latText);
144+
double longitude = Double.parseDouble(lonText);
145+
setLocation(latitude, longitude, "Manual");
146+
} catch (NumberFormatException e) {
147+
showSnackbar(getString(R.string.enter_valid_coordinates));
148+
}
149+
}
150+
151+
private void requestSdkLocationOnce() {
152+
if (isLocationPermissionGranted()) {
153+
performSdkLocationRequest();
154+
} else {
155+
userRequestedSdkLocation = true;
156+
locationPermissionLauncher.launch(new String[]{
157+
Manifest.permission.ACCESS_FINE_LOCATION,
158+
Manifest.permission.ACCESS_COARSE_LOCATION
159+
});
160+
}
161+
}
162+
163+
private void performSdkLocationRequest() {
164+
binding.lastSetLocationLabel.setText(R.string.requesting_location_sdk);
165+
ModuleLocation.instance().getLocationServices().requestLocationUpdate();
166+
showSnackbar(getString(R.string.sdk_requested_location));
167+
}
168+
169+
private void stopSdkLocationUpdates() {
170+
ModuleLocation.instance().getLocationServices().stopLocationUpdates();
171+
binding.lastSetLocationLabel.setText(R.string.location_updates_stopped);
172+
showSnackbar(getString(R.string.stopped_location_updates));
173+
}
174+
175+
private void requestCurrentLocation() {
176+
if (isLocationPermissionGranted()) {
177+
fetchDeviceLocation();
178+
} else {
179+
userRequestedCurrentLocation = true;
180+
locationPermissionLauncher.launch(new String[]{
181+
Manifest.permission.ACCESS_FINE_LOCATION,
182+
Manifest.permission.ACCESS_COARSE_LOCATION
183+
});
184+
}
185+
}
186+
187+
@SuppressWarnings("MissingPermission")
188+
private void fetchDeviceLocation() {
189+
binding.useCurrentLocation.setEnabled(false);
190+
binding.useCurrentLocation.setText(R.string.fetching_location);
191+
192+
locationListener = new LocationListener() {
193+
@Override
194+
public void onLocationChanged(@NonNull Location location) {
195+
locationManager.removeUpdates(this);
196+
setLocation(location.getLatitude(), location.getLongitude(), "Device");
197+
binding.useCurrentLocation.setEnabled(true);
198+
binding.useCurrentLocation.setText(R.string.use_current_location);
199+
}
200+
201+
@Override
202+
public void onProviderDisabled(@NonNull String provider) {
203+
showSnackbar(getString(R.string.location_not_available));
204+
binding.useCurrentLocation.setEnabled(true);
205+
binding.useCurrentLocation.setText(R.string.use_current_location);
206+
}
207+
};
208+
209+
String provider = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
210+
? LocationManager.GPS_PROVIDER
211+
: LocationManager.NETWORK_PROVIDER;
212+
213+
locationManager.requestSingleUpdate(provider, locationListener, null);
214+
}
215+
216+
// --- Permission Helpers ---
217+
218+
private boolean isLocationPermissionGranted() {
219+
return ContextCompat.checkSelfPermission(this,
220+
Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
221+
|| ContextCompat.checkSelfPermission(this,
222+
Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED;
223+
}
224+
225+
private void showPermissionDeniedAlert() {
226+
new MaterialAlertDialogBuilder(this)
227+
.setTitle(R.string.location_permission_required)
228+
.setMessage(R.string.location_permission_failure)
229+
.setNeutralButton(R.string.open_settings, (dialog, which) -> {
230+
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
231+
intent.setData(Uri.parse("package:" + getPackageName()));
232+
startActivity(intent);
233+
})
234+
.show();
235+
}
236+
237+
private void showSnackbar(String message) {
238+
Snackbar.make(binding.getRoot(), message, Snackbar.LENGTH_SHORT).show();
239+
}
240+
241+
@Override
242+
protected void onDestroy() {
243+
super.onDestroy();
244+
if (locationListener != null && locationManager != null) {
245+
locationManager.removeUpdates(locationListener);
246+
}
247+
}
248+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<shape xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:shape="rectangle">
4+
<solid android:color="#F7F7F7" />
5+
<corners android:radius="10dp" />
6+
<stroke
7+
android:width="1dp"
8+
android:color="#E6E6E6" />
9+
</shape>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<shape xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:shape="rectangle">
4+
<solid android:color="#F0F0F0" />
5+
<corners android:radius="8dp" />
6+
</shape>

samples/java_layout/src/main/res/layout/activity_dashboard.xml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,12 +179,25 @@
179179
android:layout_height="wrap_content"
180180
android:layout_marginTop="@dimen/margin_default"
181181
android:text="@string/show_push_prompt"
182-
app:layout_constraintBottom_toTopOf="@id/view_logs_button"
182+
app:layout_constraintBottom_toTopOf="@id/location_test_button"
183183
app:layout_constraintEnd_toEndOf="parent"
184184
app:layout_constraintStart_toStartOf="parent"
185185
app:layout_constraintTop_toBottomOf="@id/set_profile_attributes_button"
186186
app:layout_constraintWidth_max="@dimen/material_button_max_width" />
187187

188+
<Button
189+
android:id="@+id/location_test_button"
190+
style="?attr/materialButtonStyle"
191+
android:layout_width="0dp"
192+
android:layout_height="wrap_content"
193+
android:layout_marginTop="@dimen/margin_default"
194+
android:text="@string/location_test"
195+
app:layout_constraintBottom_toTopOf="@id/view_logs_button"
196+
app:layout_constraintEnd_toEndOf="parent"
197+
app:layout_constraintStart_toStartOf="parent"
198+
app:layout_constraintTop_toBottomOf="@id/show_push_prompt_button"
199+
app:layout_constraintWidth_max="@dimen/material_button_max_width" />
200+
188201
<Button
189202
android:id="@+id/view_logs_button"
190203
style="?attr/materialButtonStyle"

0 commit comments

Comments
 (0)