react-native-unified-contacts icon indicating copy to clipboard operation
react-native-unified-contacts copied to clipboard

[Android] Output multiline formatted address in Android 8+

Open paintedbicycle opened this issue 6 years ago • 29 comments

NOTE: This applies to Android 8+ but not Android 7-

One of the most frustrating things about all react-native contacts modules that I have found is that on iOS the address is formatted in a multiline display, while on Android it is formatted in one line.

If you enter an address on Android, you enter it in one single input field. When you retrieve it, all of the fields are outputted into one line. There seems to be ways of returning a formatted address, but in my use of several react-native libraries, they all return it in one line.

It would be great if both iOS and Android return exactly the same formatted address and accept exactly the same input from the developer. There are ways to format an address properly in Android. For example a quick search returns this: https://stackoverflow.com/questions/32914662/how-to-format-an-address-for-multiline-display

So,

  1. We should allow both iOS and Android to receive a multiline formatted address from the developer and save it into the respective platforms in whatever format those platforms want
  2. We should return multiline formatted address in exactly the same format from both iOS and Android to the developer. Optionally, we should also return the raw address string from Android for compatibility with whatever apps people are building.

paintedbicycle avatar Mar 22 '18 09:03 paintedbicycle

This is my vote for the next more important feature.

Here is an image from my app: screen shot 2018-05-13 at 5 47 00 pm

What's happening here, is that there is a mismatch between iOS and Android on the output of the contacts.

On iOS, the contact's postal address comes out as the ReadMe documentation states - in discreet pieces of data. On Andoird, the contact's postal address comes out all on one line.

This makes it very hard for me to properly format the address. It is, however, the default on Android.

I've been doing some research though, and Android does have geocoding and reverse geocoding libraries. And Google has built address libraries for Android. Here is a quick summary of my research:

This seems to be an external Google library for addresses that is compatible with Android: https://github.com/googlei18n/libaddressinput/tree/master/android

This seems to be an address manipulation library in Android (could be a good bet?): https://developer.android.com/reference/android/location/Address

This is a general purpose search of addresses given a name (i.e. does a search and returns a formatted address from a string): https://developer.android.com/reference/android/location/Geocoder#getfromlocationname

paintedbicycle avatar May 13 '18 16:05 paintedbicycle

I've kept researching this and it almost appears as if the structured postal address fields should indeed output without having to do anything too crazy, but perhaps we're not referencing it correctly.

https://stackoverflow.com/questions/3609700/get-postal-address-from-a-contact-using-contactscontract-api-on-android

https://github.com/wix/react-native-paged-contacts/blob/master/android/src/main/java/com/wix/pagedcontacts/contacts/Items/PostalAddress.java

For whatever reason, the data coming back in our call to StructredPostal is empty when it should have the individual pieces of data of the postal address. If I'm understanding this correctly.

paintedbicycle avatar May 16 '18 12:05 paintedbicycle

I took a bit of a look at this as well and thought the same thing. We must not be using the right methods and classes because it appears to have native support for each piece of an address in the SDK.

joshuapinter avatar May 16 '18 15:05 joshuapinter

Yeah! Hopefully that makes it a lot easier. Just have to re-implement structuredPostal in whatever way is required, rather than implementing a whole new set of APIs.

paintedbicycle avatar May 16 '18 15:05 paintedbicycle

Hey @paintedbicycle, I looked into this a little further and I'm completely baffled. It appears that the Contacts app in Android 8.1 doesn't let you enter in a structured postal address. You can see in these screen shots that it only accepts a single line:

screenshot 2018-05-23 17 08 25

screenshot 2018-05-23 17 09 22

screenshot 2018-05-23 17 09 29

When fetching the various pieces of a structured postal address, they are all null except for the street field, which is where everything is entered.

I did some more searching but couldn't find anything obvious. Is this normal? I haven't tested it with any other device or with real contacts that have structured postal addresses entered already.

joshuapinter avatar May 23 '18 23:05 joshuapinter

Look at this entry, it doesn't say much it seems to confirm that by default everything is added into the Street field: https://www.davdroid.com/faq/entering-structured-addresses/

Which makes no sense to me.

joshuapinter avatar May 23 '18 23:05 joshuapinter

So I tested this on an Android phone I bought for testing purposes. It's a Moto that's running 7.1.1. And getting the addresses works perfect on it.

Here's what the return looks like for a typical address:

screenshot 2018-05-23 17 31 07

But, when you go to the Contacts app there's separate fields for each piece of a Structured Postal, like City, Postal Code, etc.

2018-05-23 17_41_29

If you can confirm this that would be good.

Not sure what the next steps are with this. Let me know what you think!

joshuapinter avatar May 23 '18 23:05 joshuapinter

Hey! Yes, here is what I know:

  1. There seems to have been a UI change for entering contacts with Android 8 to a single line
  2. There also seems to be several different APIs and ways to get contacts from Android. I don't know if some are newer / more complete. I struggle with the Android docs formatting as they often don't mention version nor have examples. Maybe there is a more up-do-date API.
  3. There are several other general Android functions including "Address" that seem made to pull structured fields out of a string address (https://developer.android.com/reference/android/location/Address) (i.e. it would do a lookup) or Geocoder https://developer.android.com/reference/android/location/Geocoder#getfromlocationname ("getFromLocationName"). We could potentially use this to backfill.
  4. I've searched other contact libraries (WIX, RN Contacts) and they don't seem to have any mention of this in their Readme or issues and so either they have the same bug, or they already solved it and the app developer doesn't need to do anything special.

Paul

paintedbicycle avatar May 24 '18 07:05 paintedbicycle

Can you test out the other two libraries’ on Android 8 and see if they have the same issue? I looked through their code and didn’t find anything so I want to see if they exhibit the same issue.

On May 24, 2018, 1:57 AM -0600, Paul Wright [email protected], wrote:

Hey! Yes, here is what I know:

  1. There seems to have been a UI change for entering contacts with Android 8 to a single line
  2. There also seems to be several different APIs and ways to get contacts from Android. I don't know if some are newer / more complete. I struggle with the Android docs formatting as they often don't mention version nor have examples. Maybe there is a more up-do-date API.
  3. There are several other general Android functions including "Address" that seem made to pull structured fields out of a string address (https://developer.android.com/reference/android/location/Address) (i.e. it would do a lookup) or Geocoder https://developer.android.com/reference/android/location/Geocoder#getfromlocationname ("getFromLocationName"). We could potentially use this to backfill.
  4. I've searched other contact libraries (WIX, RN Contacts) and they don't seem to have any mention of this in their Readme or issues and so either they have the same bug, or they already solved it and the app developer doesn't need to do anything special.

Paul — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or mute the thread.

joshuapinter avatar May 24 '18 13:05 joshuapinter

Yes, I'll investigate that. Does either of these look fit for purpose?

https://developer.android.com/reference/android/location/Address

https://developer.android.com/reference/android/location/Geocoder#getfromlocationname

paintedbicycle avatar May 24 '18 13:05 paintedbicycle

There are other people doing this: https://stackoverflow.com/questions/32914662/how-to-format-an-address-for-multiline-display

Also: Quick update - Wix does not return multiline, only single line. Will try next one out.

paintedbicycle avatar May 24 '18 13:05 paintedbicycle

I looked at both Address and Geocoder yesterday but that would be like us working backwards. Geocoder for example returns an Array of possible addresses from a location name or lat/long.

There's gotta be a better way or something native to Android 8 to handle this. There's no way that Android 8 just changes an address UI to only accept a single line and doesn't provide a way to enter full addresses or convert them to a full addresses.

Thanks for checking, so far we have:

Wix: ❌ Contacts: ❓

joshuapinter avatar May 24 '18 13:05 joshuapinter

Ha, stumbled on your Issue / PRs to Contacts: https://github.com/rt2zz/react-native-contacts/issues/266

joshuapinter avatar May 24 '18 13:05 joshuapinter

Haha, yes it's literally been 3 months of me trying to get this to work! "Use react native they said. It'll save time, they said"

paintedbicycle avatar May 24 '18 13:05 paintedbicycle

The oddest thing is that I can't seem to find others confused that the latest Android version doesn't allow you to specifically enter City, Province, Postal Code, etc. into their Contacts app.

joshuapinter avatar May 24 '18 13:05 joshuapinter

Looks like React Native Contacts only returns a flat string as well

paintedbicycle avatar May 24 '18 13:05 paintedbicycle

Can we set up a few test cases?

  • Address API
  • Geocoder API
  • https://stackoverflow.com/questions/32914662/how-to-format-an-address-for-multiline-display
  • Maybe: https://stackoverflow.com/questions/3609700/get-postal-address-from-a-contact-using-contactscontract-api-on-android

If you can set up a test getting those APIs at least loading, I can then jump in and play with methods and consoling data, testing ideas. But I'd need help getting them installed and up and running.

Do you see any other ways of doing this?

paintedbicycle avatar May 24 '18 13:05 paintedbicycle

Before we go down the path of hacking this together, we need to find official documentation around this change. It's a major change to the stock Android SDK. It's either a bug or there should be an article about how to handle it.

joshuapinter avatar May 24 '18 14:05 joshuapinter

That's fair. I've done a lot of searching on this, going down many rabbit holes trying to find the docs you're referring to. Maybe you'll have more luck. Or perhaps there is someone we can ask?

paintedbicycle avatar May 24 '18 14:05 paintedbicycle

This might be a non-issue. All of my real contacts work great. This might only be an issue in the Contacts app in the emulators. If I get my hands on a real Android 8 device I'll test it on that. You should try and do the same.

Otherwise, let me know if you find anything on this issue.

joshuapinter avatar May 24 '18 14:05 joshuapinter

My dad is actually running Android 8.1 on his phone. He confirmed that he only gets the Street field in the Contacts app, too.

Baffling....

joshuapinter avatar May 24 '18 14:05 joshuapinter

That's for when he enters a new address, right? Yes I'm asking two friends to try to do the same and send me screenshots.

I guess that part doesn't have anything to do with RNUC - it might still work? I dunno.

paintedbicycle avatar May 24 '18 14:05 paintedbicycle

That's right. So, the native Contacts app stores new addresses in the Street field and that's it, it seems.

joshuapinter avatar May 24 '18 14:05 joshuapinter

Ok so returning to this, I've spent a lot of time looking around and basically the Wix contacts library isn't actively maintained anymore (according to one of the tickets) and the react native contacts is seeing a few updates, but not a lot of attention. And the rest of the modules aren't being maintained. So, it's no surprise this hasn't been solved. First, most are just using name and email (not postal) and second, there's not a lot of movement. So, perhaps we'll trailblaze here.

First, it looks like "Address" (https://developer.android.com/reference/android/location/Address) is a library on Android for spitting out the relevant parts of a formatted address from an address.

getCountryName()
Returns the localized country name of the address, for example "Iceland", or null if it is unknown.
getCountryCode()
Returns the country code of the address, for example "US", or null if it is unknown.
getLocality()
Returns the locality of the address, for example "Mountain View", or null if it is unknown.
getPostalCode()
Returns the postal code of the address, for example "94110", or null if it is unknown.

So, once Address has an address, it can format it for us. We'd use those methods to return them into what we return to the user. This may be all we need.

But, we first need to feed an address into Address. And I'm not 100% sure how to do that. Help?

It might just take it as-is from the user, but I can't find examples of how to set up the Address object (class). If we can figure this out, it would be good to just test it out and see what it outputs. It might be all we need. This Address class might take an address string and help us format it. But again, I can't figure out how to instantiate the class with data. Need some help here.

Now, if Address needs some sort of already formatted address, there are options:

  1. Geocoder is one option, but I don't think it'll work for is, as it can't get the full street address. It takes Lat/Long and gives address (or vice-versa) (Lat/Long isn't good enough for fine address (including street number). It is also silly because we already have a an address string with most of the address (coming from the user).

  2. It appears that this could be a job for libaddressinput. Also a Google library. https://github.com/googlei18n/libaddressinput

This is an address parsing library. And there are examples of people using it for this:

Check out googlei18n/libaddressinput: Google’s postal address library, powering Android and Chromium. There are two modules in the project :android and :common. You should only need :common to format the address for multi-line display.

import android.location.Address;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import com.google.i18n.addressinput.common.AddressData;
import com.google.i18n.addressinput.common.FormOptions;
import com.google.i18n.addressinput.common.FormatInterpreter;
...
public static String getFormattedAddress(@NonNull final Address address, 
                                         @NonNull final String regionCode) {
    final FormatInterpreter formatInterpreter
            = new FormatInterpreter(new FormOptions().createSnapshot());
    final AddressData addressData = (new AddressData.Builder()
            .setAddress(address.getThoroughfare())
            .setLocality(address.getLocality())
            .setAdminArea(address.getAdminArea())
            .setPostalCode(address.getPostalCode())
            .setCountry(regionCode) // REQUIRED
            .build());
    // Fetch the address lines using getEnvelopeAddress,
    List<String> addressFragments = formatInterpreter.getEnvelopeAddress(addressData);
    // join them, and send them to the thread.
    return TextUtils.join(System.getProperty("line.separator"),
            addressFragments);
}

From: https://stackoverflow.com/questions/32914662/how-to-format-an-address-for-multiline-display

Because I don't know Java, I can't fully understand where the user's address is coming from in that example, but the person does state that it "should only need :common to format the address for multi-line display."

So,

  1. Can we set up Address and see if that's all we need? 2 If it needs to be fed an address object, can we try libaddressinput? (Hopefully this step isn't required)

Paul

paintedbicycle avatar Jun 05 '18 14:06 paintedbicycle

I think you've summed it up perfectly. I'm still blown away that Google would change their Contacts app to just accept one line.

I'm tied up with work for the next bit but it looks like you're so close to at least testing this out and see if you can get it to work. I took a look at the Address lib and there's a method called setAddressLine(int index, String line) that might be worth checking out to start with. Try passing it like setAddressLine(0, 123 Wewatta St, Denver, Colorado, USA) and see what happens.

If you run into trouble, let me know, and I'll try to help out as much as possible but like I said, I'm tied up with work for the next bit.

joshuapinter avatar Jun 06 '18 03:06 joshuapinter

I just discovered this issue where some versions of Android store the whole address in a single string. Seems it is the newer versions of Android that store a single string. Did you all figure out a solution for this? Does Android have a built in way to convert single string into separate fields? I might try this out: https://github.com/openvenues/libpostal

ddnn55 avatar May 14 '19 07:05 ddnn55

Yeah, I have to admit this is pretty frustrating. It seems Android 8+ has this format. And there didn't seem to be any easy way of getting a formatted address back. I'm not sure if any of the other contact libraries have tacked this either. In my research about a year ago, I found that non of them had touched it.

All ways of fixing this I could find relied on API calls to Google or some other library. And then, what you'd receive back would be a set of results..i.e. one or more results for an address and it'd just be the closest guess, not a guaranteed exact match.

So, we stopped there. Would love to see what other apps do...but I assume they are just using the one-line address box if Android 8+. Trouble is, if your app needs validation or a guaranteed address structure (i.e. mailing physical goods), it's not good enough to have a one-liner. Such a strange choice by Google.

paintedbicycle avatar May 15 '19 12:05 paintedbicycle

So I think it’s actually a certain version of the Google Contacts app that started storing just a single string. Some Android vendors might have their own Contacts apps that use fields. 3rd party apps can keep storing fields too. One’s own app could even write into the separate fields I believe.

I speculate they may have moved to a single string because the concept of “street, city, state, post code” and its variations are actually pretty US-centric and don’t work so well in a lot of the world.

My MVP plan for now is to basically ignore the problem, load the address fields into my on-screen fields, then validate (all I do is require non empty street, city, and country, which is what my fulfillment provider requires) and let the user fix problems, e.g. move everything but the street out of the street field and into the correct fields. At least it is on screen, so the user can transcribe without having to switch between apps.

Also I want to save successfully used addresses so they are available in one tap the next time around. If I’m being evil, I could save them back into the Android contacts DB as separate fields (then, it seems, even the modern Google Contacts app will show and allow editing of the separate fields) but that would require Contacts write permission.

On Wed, May 15, 2019 at 7:26 PM Paul Wright [email protected] wrote:

Yeah, I have to admit this is pretty frustrating. It seems Android 8+ has this format. And there didn't seem to be any easy way of getting a formatted address back. I'm not sure if any of the other contact libraries have tacked this either. In my research about a year ago, I found that non of them had touched it.

All ways of fixing this I could find relied on API calls to Google or some other library. And then, what you'd receive back would be a set of results..i.e. one or more results for an address and it'd just be the closest guess, not a guaranteed exact match.

So, we stopped there. Would love to see what other apps do...but I assume they are just using the one-line address box if Android 8+. Trouble is, if your app needs validation or a guaranteed address structure (i.e. mailing physical goods), it's not good enough to have a one-liner. Such a strange choice by Google.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/joshuapinter/react-native-unified-contacts/issues/52?email_source=notifications&email_token=AABNPJMWRVOBPPKJSP5QOTTPVP6OFA5CNFSM4EW274X2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODVOPWTI#issuecomment-492632909, or mute the thread https://github.com/notifications/unsubscribe-auth/AABNPJLEYV7TDC4NZLZQR2TPVP6OFANCNFSM4EW274XQ .

ddnn55 avatar May 15 '19 13:05 ddnn55

Also I tried libpostal, it’s pretty cool and should work in some cases to turn the string into separate fields. But the fields come out reformatted and rewritten, and it will do wrong things some of the time. I’d love to use it, but it takes 2GB of binary size (ML model) so it’s probably $50/month at least to run on a server. Can’t really run it in app.

ddnn55 avatar May 15 '19 13:05 ddnn55