Skip to content

🚀 WearOS support #1197

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
masus04 opened this issue Jan 22, 2023 · 20 comments · May be fixed by #1689
Open

🚀 WearOS support #1197

masus04 opened this issue Jan 22, 2023 · 20 comments · May be fixed by #1689
Labels
P3 Issues that we currently consider unimportant. platform: android Issue is related to the Android platform. type: enhancement New feature or request

Comments

@masus04
Copy link

masus04 commented Jan 22, 2023

🚀 WearOs Support Feature Request

As WearOS is based on android and works quite similar, it would be great to have geolocator support it.
So far none of the Flutter location packages are supporting WearOS and this could be a great argument to use geolocator.

Contextualize the feature

Geolocator supports most relevant platforms on which location is required. The only exception being Apple watchOS and Android WearOS. This feature request seeks to address one of these. Doing so would also make geolocator the first flutter plugin that supports location integration on wearOS devices.

Describe the feature

Ideally the full feature set that is currently supported on mobile android should be supported. If there are limitations, the key features would be a great start.

I suspect the fact that geolocator does not currently work on wearOS, even though it works great on android is mostly due to different permission / service requirements.

Platforms affected (mark all that apply)

  • ☒ 🤖 Android

Test Application

I have set up a very minimal test application to showcase the current behaviour: https://github.com/masus04/Flutter-WearOS-Location/blob/main/README.md

The same behaviour can be observed when running the application on an emulator in Android Studio or a physical device.

When running this application on Android mobile / wearOS, the following log is printed:

Android Mobile

Launching lib/main.dart on sdk gphone64 x86 64 in debug mode...
Running Gradle task 'assembleDebug'...
✓  Built build/app/outputs/flutter-apk/app-debug.apk.
D/FlutterGeolocator( 7053): Attaching Geolocator to activity
D/FlutterGeolocator( 7053): Creating service.
D/FlutterGeolocator( 7053): Binding to location service.
D/FlutterGeolocator( 7053): Geolocator foreground service connected
D/FlutterGeolocator( 7053): Initializing Geolocator services
D/FlutterGeolocator( 7053): Flutter engine connected. Connected engine count 1
Debug service listening on ws://127.0.0.1:36951/LqjelzE7zxs=/ws
Syncing files to device sdk gphone64 x86 64...
I/wearos_location( 7053): Compiler allocated 4533KB to compile void android.view.ViewRootImpl.performTraversals()
I/flutter ( 7053): Initializing GeoLocator
E/FlutterGeolocator( 7053): Geolocator position updates started
E/SurfaceSyncer( 7053): Failed to find sync for id=0
W/Parcel  ( 7053): Expecting binder but got null!
D/EGL_emulation( 7053): app_time_stats: avg=21.08ms min=3.63ms max=89.87ms count=20
D/EGL_emulation( 7053): app_time_stats: avg=7.85ms min=1.73ms max=29.29ms count=24
D/EGL_emulation( 7053): app_time_stats: avg=6.34ms min=1.55ms max=32.13ms count=23
D/EGL_emulation( 7053): app_time_stats: avg=5.31ms min=1.67ms max=27.66ms count=28
I/flutter ( 7053): Current Speed: 1.3889987468719482 m/s
I/flutter ( 7053): Current Speed: 1.3889987468719482 m/s
D/EGL_emulation( 7053): app_time_stats: avg=151.05ms min=1.17ms max=1868.30ms count=13
I/flutter ( 7053): Current Speed: 1.3889987468719482 m/s
D/EGL_emulation( 7053): app_time_stats: avg=2997.02ms min=2997.02ms max=2997.02ms count=1
I/flutter ( 7053): Current Speed: 1.3889987468719482 m/s
D/EGL_emulation( 7053): app_time_stats: avg=2985.55ms min=2985.55ms max=2985.55ms count=1

WearOS

Launching lib/main.dart on sdk gwear x86 in debug mode...
Running Gradle task 'assembleDebug'...
✓  Built build/app/outputs/flutter-apk/app-debug.apk.
Installing build/app/outputs/flutter-apk/app.apk...
D/FlutterGeolocator( 4135): Attaching Geolocator to activity
D/FlutterGeolocator( 4135): Creating service.
D/FlutterGeolocator( 4135): Binding to location service.
D/FlutterGeolocator( 4135): Geolocator foreground service connected
D/FlutterGeolocator( 4135): Initializing Geolocator services
D/FlutterGeolocator( 4135): Flutter engine connected. Connected engine count 1
Debug service listening on ws://127.0.0.1:39289/2bRo_AjON9Q=/ws
Syncing files to device sdk gwear x86...
I/flutter ( 4135): Initializing GeoLocator
E/FlutterGeolocator( 4135): Geolocator position updates started
I/wearos_locatio( 4135): Waiting for a blocking GC ProfileSaver
I/flutter ( 4135): The following error occurred: The location service on the device is disabled.
I/flutter ( 4135): #0      GeolocatorAndroid.getPositionStream.<anonymous closure> (package:geolocator_android/src/geolocator_android.dart:187:9)
I/flutter ( 4135): (elided 26 frames from dart:async)

This is with the latest Flutter & dependencies at the time of this issue

@masus04
Copy link
Author

masus04 commented Jan 22, 2023

This is a follow up to #785

@talhazengin

This comment was marked as off-topic.

@JeroenWeener JeroenWeener added type: enhancement New feature or request platform: android Issue is related to the Android platform. P3 Issues that we currently consider unimportant. labels Jul 3, 2023
@masus04
Copy link
Author

masus04 commented Aug 17, 2023

Any updates on this? There is still not a single Location package that supports WearOS.

@masus04
Copy link
Author

masus04 commented Mar 6, 2024

While wearOS is still not officially supported by flutter, most use cases can be covered by third party packages like this one and I personally have multiple apps productive on the play store.

However, there is still no location package available for wearOS applications, signifficantly reducing the appeal of using Flutter for wearOS. Supporting this use case could be huge for Flutter on WearOS.

@masus04
Copy link
Author

masus04 commented Mar 21, 2024

I am considering implementing this feature in the near future. Can you point me to what would have to be done in order to do so?

This woul dbe the first time I work on Flutter plugins, so I would really appreciate the help.

Also, would a wearOS implementation be part of the geolocator_android federated plugin, or would it be implement as a separate federated plugin?

@SkylL3r
Copy link

SkylL3r commented Mar 24, 2024

It would be so great to see geolocator working on WearOS !
I just started a WearOS project on my own with geolocator as its key feature. I was surprised to see no location packages works on WearOS so far...

As of the WearOS implementation for geolocator, I guess it could be part of geolocator_android plugin, but might not be the easiest way though.

If any help is necessary I would be glad to join, even if I'm in the same situation as you are regarding Flutter packages.

@tgritter
Copy link

@masus04 @SkylL3r I need this too for my project and would also being willing to chip in. Yeah I believe the geolocator_android is the place to start. I was able to build a local bridge between android and flutter to get it working on my emulator (can't see to get it working my device though), that's where I added my code.

@tgritter
Copy link

@masus04 @SkylL3r I was able to get the location working on WearOS writing my own code (not using this package). The trick is to use the FusedLocationProviderClient instead of the regular location provider (a smartwatch gets its GPS data differently than a phone). Let me know if you want my code, and/or if we should add it to this package for future users.

@SkylL3r
Copy link

SkylL3r commented Mar 31, 2024

@tgritter Indeed, I also was able to get the location on WearOs using FusedLocationClientProvider last night on my own plugin.
But I admit my own is pretty standard and maybe not quite strong enough on permissions checks and stuff based on what geolocator_android does.

So I'd be interested in seeing your code as a comparison and/or show you mine.

I also keep thinking it would be a great addition to this package for future users. 👍🏽

@tgritter
Copy link

tgritter commented Mar 31, 2024

@SkylL3r Here is my code:

build.gradle

implementation 'com.google.android.gms:play-services-location:18.0.0'

MainActivity.kt

import android.Manifest
import android.content.pm.PackageManager
import android.os.Build
import androidx.annotation.NonNull
import androidx.core.app.ActivityCompat
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant
import android.location.Location
import android.os.Bundle
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationServices
import io.flutter.plugin.common.EventChannel
import com.google.android.gms.location.LocationCallback
import com.google.android.gms.location.LocationResult
import com.google.android.gms.location.LocationRequest
import java.util.concurrent.TimeUnit

class MainActivity : FlutterActivity() {
    private val CHANNEL = "location_permission"
    private val LOCATION_CHANNEL = "location_updates"
    private val PERMISSION_REQUEST_CODE = 123
    private lateinit var fusedLocationClient: FusedLocationProviderClient
    private var eventSink: EventChannel.EventSink? = null
    private lateinit var locationCallback: LocationCallback // Declare locationCallback as a class property

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
    }

    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)

        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
            .setMethodCallHandler { call, result ->
                when (call.method) {
                    "requestPermission" -> {
                        requestLocationPermission(result)
                    }
                    "checkPermission" -> {
                        result.success(checkLocationPermission())
                    }
                    else -> result.notImplemented()
                }
            }

        EventChannel(flutterEngine.dartExecutor.binaryMessenger, LOCATION_CHANNEL)
            .setStreamHandler(object : EventChannel.StreamHandler {
                override fun onListen(arguments: Any?, eventSink: EventChannel.EventSink?) {
                    [email protected] = eventSink
                    startLocationUpdates()
                }

                override fun onCancel(arguments: Any?) {
                    stopLocationUpdates()
                }
            })
    }

    private fun requestLocationPermission(result: MethodChannel.Result) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (ActivityCompat.checkSelfPermission(
                    this,
                    Manifest.permission.ACCESS_FINE_LOCATION
                ) == PackageManager.PERMISSION_GRANTED
            ) {
                result.success(true)
                return
            }
            ActivityCompat.requestPermissions(
                this,
                arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
                PERMISSION_REQUEST_CODE
            )
        }
    }

    private fun checkLocationPermission(): Boolean {
        return ActivityCompat.checkSelfPermission(
            this,
            Manifest.permission.ACCESS_FINE_LOCATION
        ) == PackageManager.PERMISSION_GRANTED
    }

    private fun startLocationUpdates() {
        if (checkLocationPermission()) {
            val locationRequest = getLocationRequest()
            locationCallback = object : LocationCallback() {
                override fun onLocationResult(locationResult: LocationResult) {
                    for (location in locationResult.locations) {
                        eventSink?.success("${location.latitude} ${location.longitude}")
                    }
                }
            }
            fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, null)
        }
    }

    private fun stopLocationUpdates() {
        fusedLocationClient.removeLocationUpdates(locationCallback)
    }

    private fun getLocationRequest(): LocationRequest {
        return LocationRequest.create().apply {
            interval = TimeUnit.SECONDS.toMillis(1)
            fastestInterval = TimeUnit.SECONDS.toMillis(1)
            priority = LocationRequest.PRIORITY_HIGH_ACCURACY
        }
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == PERMISSION_REQUEST_CODE) {
            if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                eventSink?.success(true)
            } else {
                eventSink?.success(false)
            }
        }
    }
}

Then you can call it in Flutter like so:

Future<void> _startLocationUpdates() async {
    try {
      await platform.invokeMethod('requestPermission');
      final bool result = await platform.invokeMethod('checkPermission');
      if (result) {
        eventChannel.receiveBroadcastStream().listen((event) {
          List<double> coords = parseCoordinates(event);
          print("Coordinates: $coords");
        });
      }
    } on PlatformException catch (e) {
      print("Failed to start location updates: '${e.message}'.");
    }
  }

@tgritter
Copy link

And yeah would be great to get this package working for future users. Although it's probably not as simple as just copying and pasting this code 😟

@momo21584
Copy link

Any news here ?
I'm also willing to help in this case.

@tgritter
Copy link

tgritter commented Oct 20, 2024

@momo21584 I haven't made a PR for this change, no. If you need GPS for the watch I would advise building it yourself using a bridge modal. The code I posted above ^ was mainly working for me. The key difference between the watch and phone is using the com.google.android.gms.location.FusedLocationProviderClient package for the watch.

@masus04
Copy link
Author

masus04 commented Oct 20, 2024

If it's not that big of a change, would it be possible to open a PR for it?

@tgritter
Copy link

@masus04 I think it would be a big change unfortunately. There would be a lot of device based logic you'd have to be very careful about. You'd probably need to test on various devices too, different watches handle GPS differently I've found. There may be other device based problems encountered too.

Unless someone really wants to take this on, I think writing a bridging module is going to be much easier for getting Flutter GPS on WearOS. A good experience too, I learned a lot about bridging writing mine!

@muratsevim
Copy link

I noticed that this error is seen because SettingsClient returns DEVELOPER_ERROR on Wear OS. I was able to get getCurrentPosition API working by modifying the status code check in FusedLocationClient.java

if (statusCode == LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE) {

                if (statusCode == LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE 
                    || statusCode == CommonStatusCodes.DEVELOPER_ERROR) {
                  requestPositionUpdates(this.locationOptions);
                } else {

Sharing in case anyone needs it. I will try to send a pull request if I get a chance but please feel free to beat me to it :)

@masus04
Copy link
Author

masus04 commented Apr 13, 2025

I noticed that this error is seen because SettingsClient returns DEVELOPER_ERROR on Wear OS. I was able to get getCurrentPosition API working by modifying the status code check in FusedLocationClient.java

flutter-geolocator/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/FusedLocationClient.java

Line 267 in a24775b

if (statusCode == LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE) {

                if (statusCode == LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE 
                    || statusCode == CommonStatusCodes.DEVELOPER_ERROR) {
                  requestPositionUpdates(this.locationOptions);
                } else {

Sharing in case anyone needs it. I will try to send a pull request if I get a chance but please feel free to beat me to it :)

That's the only change you had to make in order to get geolocator to run properly on WearOS?

That would be wonderful news 👍

@muratsevim
Copy link

Yep, I didn't test all APIs but getCurrentPosition works after that change. As far as I can tell geolocator already uses FusedLocationProviderClient, it is just not working on Wear OS because of this error.

@masus04
Copy link
Author

masus04 commented Apr 14, 2025

Yep, I didn't test all APIs but getCurrentPosition works after that change. As far as I can tell geolocator already uses FusedLocationProviderClient, it is just not working on Wear OS because of this error.

Could you open a branch & PR? I'd be happy to test it on my app/devices.

@muratsevim muratsevim linked a pull request Apr 18, 2025 that will close this issue
10 tasks
@muratsevim
Copy link

PR sent. I don't know if it fixes all issues on Wear OS but getCurrentPosition() works for me after that change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
P3 Issues that we currently consider unimportant. platform: android Issue is related to the Android platform. type: enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants