Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Format expression Android bindings #12985

Merged
merged 4 commits into from Oct 24, 2018
Merged

Format expression Android bindings #12985

merged 4 commits into from Oct 24, 2018

Conversation

LukasPaczos
Copy link
Member

Refs #12624, exposes format expression on Android.

Setting the text-field using the expression, or plain text, works as expected, however, getting the text-field property back from core reveals a couple of issues:

  • Plain text is returned as expected.
  • Any expression, other than format, is returned wrapped by format expression. This includes tokens (which are beforehand converted from plain text to expressions and this is expected).
  • When value is passed initially as the format expression, it's being returned as String, which is expected because of this implementation.

Currently, above issues cause a bunch of tests to fail.

/cc @ChrisLoer

@LukasPaczos LukasPaczos added Android Mapbox Maps SDK for Android in progress labels Sep 28, 2018
@LukasPaczos
Copy link
Member Author

Solution to above issues will require introducing a Formatted type on the Java side, and possibly separate getters and setters of text-field property for plain text and Expression + Formatted.

@ChrisLoer ChrisLoer force-pushed the formatted-text branch 2 times, most recently from 5712099 to 80014cb Compare October 3, 2018 00:04
@LukasPaczos LukasPaczos force-pushed the lp-format-expression branch 4 times, most recently from 928056e to 3d9515b Compare October 10, 2018 17:41
@LukasPaczos LukasPaczos added this to the android-v6.7.0 milestone Oct 10, 2018
@ChrisLoer ChrisLoer force-pushed the formatted-text branch 3 times, most recently from 0fc0825 to 0b1466e Compare October 10, 2018 23:07
@LukasPaczos LukasPaczos force-pushed the lp-format-expression branch 7 times, most recently from a267ad1 to 56e58b9 Compare October 11, 2018 18:21
@LukasPaczos
Copy link
Member Author

LukasPaczos commented Oct 11, 2018

This PR introduces Formatted (equivalent of the style-spec type) java object which is returned in, also newly added, SymbolLayer#getFormattedTextField value field for constant text-field properties. In order not to break semver, the SymbolLayer#getTextField value field still returns plain text in the form of String.

Expression fields returned by SymbolLayer#getFormattedTextField and SymbolLayer#getTextField both return the original Expression wrapped in Expression#format expression.

Follow up work needed after this PR lands is exposing PropertyFactory#textField method that takes Formatted as an argument and all the c++ conversion code needed. When major version release comes, we can remove SymbolLayer#getFormattedTextField and make SymbolLayer#getTextField return Formatted value type.

* Text is required to be a string literal, font-scale a number literal and text-font an array literal that holds
* names of main and backup fonts.
* All of them are required to be passed, but font-scale and text-font can be nullified
* and will default to 1.0 and an empty array respectively.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"default to 1.0 and an empty array respectively."

Instead of describing the defaults in implementation terms here, I think it would be better to describe the conceptual effect, which is that if "font-scale" or "text-font" are not set, the section will use the base "text-size" or "text-font (respectively) defined for the symbol.

* and accepts unlimited numbers of formatted sections.
* <p>
* Each sections consist of 3 elements, in this order: text, font-scale and text-font.
* Text is required to be a string literal, font-scale a number literal and text-font an array literal that holds
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"text-font" is required to be an array literal. "text" only requires an expression that returns a string and "font-scale" requires an expression that returns a number.

* "format" expressions can be used, for example, with the {@link PropertyFactory#textField(Expression)}
* and accepts unlimited numbers of formatted sections.
* <p>
* Each sections consist of 3 elements, in this order: text, font-scale and text-font.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How hard would it be to make the Java interface conform to the underlying expression format of using an array of text/option pairs? The problem with expanding the current "options" argument into two separate arguments here is that it will break backwards compatibility when/if we add support for further options (there are several that could conceivably be added, the immediate one we have a request for is to add "text-color" as a format option).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the heads up! I'll try to future-proof the builder.

@@ -62,7 +62,7 @@ public Expression getExpression() {
? Expression.Converter.convert((JsonArray) value)
: (Expression) value;
} else {
Logger.w(TAG, "not a expression, try value");
Logger.w(TAG, "not an expression, try value");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took me a moment to understand "try value"/"try expression" -- would it read easier to say "try getValue()"/"getExpression()"?

@@ -133,7 +131,6 @@ public class <%- camelize(type) %>LayerTest extends BaseActivityTest {
});
}


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: This makes the diff bigger than it needs to be.

auto section = value.sections.at(i);
auto text = jni::Make<jni::String>(env, section.text);

auto fontScale = 1.0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: auto here is hard to read (although not technically ambiguous obviously since the compiler can figure it out). I would prefer explicitly declaring the type as double. There's a rule of thumb that you should restrict using auto to cases where the type is visible on the same line -- we definitely don't live up to that rule, but it's good to keep in the back of your mind! Basically, auto is good when it saves lots of repetitive typing, but it's bad when it prevents you from having to think about what type you're working with.

fontScale = section.fontScale.value();
}

auto fontStack = jni::Array<jni::String>::New(env, 0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I saw FormattedSection::getFontStack() above and saw that it was nullable, I assumed that "null" was being used to represent "not set". But it looks here (and now I notice in the tests) that text-font: null will cause getFontStack() to return new String[] {}. That doesn't seem right -- an empty array would signify "use this FontStack that doesn't have any fonts in it" instead of "use the default font stack".

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I'm going to return null instead of an empty array for un-set stack.

platform/android/src/style/formatted.cpp Show resolved Hide resolved
}
};
Object[] actual = format(
literal("test"), null, literal("awesome"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"awesome" should be new String[]{"awesome"}
same applies for other occurrences of font-stack in the unit tests

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great catch!

@tobrun
Copy link
Member

tobrun commented Oct 12, 2018

Thanks for running with this @LukasPaczos and @ChrisLoer for the thorough review and great insights!

Took the code for a spin and seems to be working well:

screen shot 2018-10-12 at 16 16 01

Above is the a the expression copied from the unit test with some valid fonts (+ putting them in an array)

            format(
              literal("test"), null, literal(new String[] {"DIN Offc Pro Regular", "Arial Unicode MS Regular"}),
              literal("test2"), literal(1.5), null,
              literal("test3"), null, null,
              literal("test4"), literal(1.5), literal(new String[] {"DIN Offc Pro Regular", "Arial Unicode MS Regular"})
            )

An idea to improve the ease of use of this API we could expose format specific expression:

            format(
              formatEntry("test", new String[] {"DIN Offc Pro Regular", "Arial Unicode MS Regular"}),
              formatEntry("test2", 1.5),
              formatEntry("test3"),
              formatEntry("test4"), 1.5, new String[] {"DIN Offc Pro Regular", "Arial Unicode MS Regular"})
            )

This makes it slightly easier to integrate and would remove the necessity of adding null values such an improvement can be done as tail work. I'm 👍 on this PR, will leave it to @ChrisLoer for the actual 👍.

@ChrisLoer ChrisLoer force-pushed the formatted-text branch 2 times, most recently from b032b38 to fc71883 Compare October 15, 2018 19:40
@LukasPaczos LukasPaczos force-pushed the lp-format-expression branch 2 times, most recently from a4ac85b to 5ed3c05 Compare October 22, 2018 14:44
@LukasPaczos LukasPaczos changed the base branch from formatted-text to master October 22, 2018 14:45
For converting to Java types, flatten 'text-field' back to a string.
@LukasPaczos LukasPaczos force-pushed the lp-format-expression branch 4 times, most recently from 659afbb to 923dfa7 Compare October 23, 2018 15:53
@LukasPaczos
Copy link
Member Author

This one is ready for another round @tobrun @ChrisLoer. I addressed the comments and opted for a more typed builder with FormatEntry type suggested by @tobrun and extended the idea to include FormatOption which also prepares the code to accept a new set of options in the future.

@LukasPaczos
Copy link
Member Author

923dfa7 also updates the local style that we are using for testing which was causing issues when setting a text-field property.

@LukasPaczos
Copy link
Member Author

8395a5c renames format options to avoid a clash with PropertyFactory#textFont and adds the example in the test activity.

Copy link
Contributor

@ChrisLoer ChrisLoer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good to me! Will defer to @tobrun on final review.

* formatFontScale(2.0),
* formatTextFont(new String[] {"DIN Offc Pro Regular", "Arial Unicode MS Regular"})
* ),
* formatEntry(concat(literal("\n"), get("description_property")), formatFontScale(1.5)),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code example looks like it abruptly ends after formatFontScale(1.5)),, is that intentional?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch 👍

@@ -31,5 +31,5 @@ float Position::getPolarAngle(jni::JNIEnv& env, const jni::Object<Position>& pos
return position.Get(env, field);
}

} // namespace andr[oid
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😆

platform/android/src/style/formatted.cpp Show resolved Hide resolved
Copy link
Member

@tobrun tobrun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀

@LukasPaczos LukasPaczos force-pushed the lp-format-expression branch 2 times, most recently from 6607d5b to 219c370 Compare October 24, 2018 09:50
@LukasPaczos
Copy link
Member Author

Example usage of the format expression:
device-2019-03-05-121105

mapView.getMapAsync(mapboxMap -> {
  mapboxMap.setStyle(Style.MAPBOX_STREETS, style -> {
    // create header entry based on data-driven styling
    Expression.FormatEntry header = Expression.formatEntry(
      Expression.get("header_property"),
      Expression.FormatOption.formatFontScale(Expression.get("header_scale_property")),
      Expression.FormatOption.formatTextFont(Expression.get("header_font_property")));
    
    // create hardcoded text entry
    Expression.FormatEntry text = Expression.formatEntry(
      "\nsome text",
      Expression.FormatOption.formatFontScale(1.5));
    
    // create hardcoded side-note entry
    Expression.FormatEntry sideNote = Expression.formatEntry(
      " (side-note)",
      Expression.FormatOption.formatFontScale(0.75));
    
    // build format expression out of provided entries
    Expression format = Expression.format(header, text, sideNote);
    
    // create the first feature and add header data
    Feature feature1 = Feature.fromGeometry(Point.fromLngLat(0, 45));
    feature1.addStringProperty("header_property", "Header1");
    feature1.addNumberProperty("header_scale_property", 3.5);
    
    JsonArray headerFontStack1 = new JsonArray();
    headerFontStack1.add("DIN Offc Pro Bold");
    headerFontStack1.add("Arial Unicode MS Regular");
    feature1.addProperty("header_font_property", headerFontStack1);
    
    // create the second feature and add header data
    Feature feature2 = Feature.fromGeometry(Point.fromLngLat(0, -45));
    feature2.addStringProperty("header_property", "Header2");
    feature2.addNumberProperty("header_scale_property", 3.5);
    
    // reverse the font stack
    JsonArray headerFontStack2 = new JsonArray();
    headerFontStack2.add("Arial Unicode MS Regular");
    headerFontStack2.add("DIN Offc Pro Bold");
    feature2.addProperty("header_font_property", headerFontStack2);
    
    // add the source and the layer
    Source source = new GeoJsonSource("source", FeatureCollection.fromFeatures(new Feature[] {feature1, feature2}));
    style.addSource(source);
    Layer layer = new SymbolLayer("layer", "source")
      .withProperties(
        PropertyFactory.textField(format)
      );
    style.addLayer(layer);
  });
});

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Android Mapbox Maps SDK for Android
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants