Skip to content

Code-behind fragment and widget access fixes #1378

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

Merged
merged 2 commits into from
Mar 9, 2018

Conversation

grendello
Copy link
Contributor

@grendello grendello commented Mar 7, 2018

Fragment handling code partially taken from #1302

Fragments are treated as normal widget, with the difference that they have to be
found using FragmentManager.FindFragmentById instead of the usual
Activity.FindViewById method.

Each generated class also gets a property named Widget which returns the
actual Android widget as found in the layout file. This allows us to keep
hierarchical nature of the XML code (thus handling nested widgets with duplicate
ids gracefully) while being able to access the parent widget itself.

The commit introduces a new attribute called tools:managedType which is used
to specify the element associated property's type. We cannot use tools:class
for this purpose since the layout root element already uses it to specify the
name of the generated code-behind class and if the element had android:id on
it, we would end up using the activity's type for the root element's property in
the generated code, instead of its actual type.

@grendello grendello added the do-not-merge PR should not be merged. label Mar 7, 2018
@grendello grendello requested review from jonpryor and dellis1972 March 7, 2018 17:29
@jonpryor
Copy link
Contributor

jonpryor commented Mar 7, 2018

Missing: Documentation update. ;-)

There's also a philosophical question which we've debated on (private?) Slack and Issue #1302: What API are we actually providing, and should this be consistent?

For example, if a .axml file has this fragment:

<TextView
    android:id="@+id/first_text_view"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:editable="false"
/>

What API are we exporting? There are three options, one of which must be documented:

Option 1: "Direct" binding- We bind as it is in the .axml file:

internal TextView first_text_view {get;}

This affords the "obvious" usage:

protected override void OnCreate (Bundle bundle) {
    first_text_view.Click += (o, e) => { /* ... */ };
}

However, this option won't -- can't! -- support .axml files which have multiple //@android:id elements with the same value. Presumably, we'd bind the first one found.

Option 2: "Indirect" binding: we bind as an indirect type in the .axml file:

internal __first_text_view_Views first_text_view {get;}
internal class __first_text_view_Views {
    public TextView Value {get;} // name?
}

This doesn't support the obvious usage of Option 1, but means that if the .axml declares multiple elements with the same //@android:id attribute, we can list them all in a consistent manner.

Usage is...weird:

protected override void OnCreate (Bundle bundle) {
    first_text_view.Value.Click += (o, e) => { /* ... */ };
    // WTF is `Value`? What do we call the "other" `//@android:id` "overloads"? Value2, Value3, etc.?
}

Option 3: Do both! Use Option 1 when there's only one //@android:id value within the file, and Option 2 otherwise.

This seems "obvious", but means there is no consistent API; it's dependent on how many //@android:id elements share the same value.

This could be acceptable -- maybe even preferable?! -- but will complicate the documentation process.

@@ -121,7 +122,7 @@ public override bool Execute ()

void GetLineInfo (IXmlLineInfo linfo, out int line, out int column)
{
if (linfo == null || linfo.HasLineInfo ()) {
if (linfo == null || !linfo.HasLineInfo ()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I half wonder if this should be a separate PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nah. It's a one-liner and really doesn't deserve special treatment.

@grendello
Copy link
Contributor Author

grendello commented Mar 7, 2018

For example, if a .axml file has this fragment:

<TextView
   android:id="@+id/first_text_view"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:editable="false"
/>

What API are we exporting? There are three options, one of which must be documented:

Option 1: "Direct" binding- We bind as it is in the .axml file:

internal TextView first_text_view {get;}

This affords the "obvious" usage:

protected override void OnCreate (Bundle bundle) {
  first_text_view.Click += (o, e) => { /* ... */ };
}

However, this option won't -- can't! -- support .axml files which have multiple //@android:id elements with the same value. Presumably, we'd bind the first one found.

This option also doesn't take into account the hierarchy. I realize we can flatten it, but then you lose the contextual information from the axml and I think having this hierarchical context is, in fact, an advantage of our implementation over Android.

Usage is...weird:

protected override void OnCreate (Bundle bundle) {
  first_text_view.Value.Click += (o, e) => { /* ... */ };
   // WTF is `Value`? What do we call the "other" `//@android:id` "overloads"? Value2, Value3, etc.?

I don't know about "weird" here. This is used in all kinds of OOP GUI libraries where there's the notion of a container and its children or properties. As for the "other" overloads - the problem doesn't exist here because the other ones are nested in other parents and there's always unique and uniform path to access them:

protected override void OnCreate (Bundle bundle) {
   // MyTextView is the .axml widget id
   first_layout.MyTextView.Widget.Click += (o, e) => { };
   second_layout.MyTextView.Widget.Click += (o, e) => { };
}

As for sibling widgets with the same id, they aren't directly accessible in Android either - the first one is picked and we bind that one. The other dupes are reachable by enumerating children of the layout/parent/container widget.
Note: I used Widget above as the commit implements because I think it better implements the "meaning" of the property. Also, Widget exists only if the addressed widget has child widgets, otherwise it's a leaf widget and it has the actual Android type. This is pretty consistent and easy to document.

}

Option 3: Do both! Use Option 1 when there's only one //@android:id value within the file, and Option 2 otherwise.

This seems "obvious", but means there is no consistent API; it's dependent on how many //@android:id elements share the same value.

This could be acceptable -- maybe even preferable?! -- but will complicate the documentation process.

You seem to focus only on the duplicate IDs, but they are just part of the entire picture. I really think that the biggest advantage of my approach is the reflection of the hierarchical nature of the layout and it blends in nicely within the overall OOP paradigm of XA.

@grendello grendello force-pushed the code-behind-fixes branch 5 times, most recently from 48f2a7a to 9c93623 Compare March 8, 2018 13:50
@grendello grendello removed the do-not-merge PR should not be merged. label Mar 8, 2018

<a name="" class="injected"/></a>

# Preparing to use Code Behind

In order to make use of this new feature there are a few changes which are required.
An axml/xml file that you want to associate with an activity needs to be modified to
include a few extra xml attributes on the root layout element.
An ``axml/xml`` file that you want to associate with an activity needs to be modified to
Copy link
Contributor

Choose a reason for hiding this comment

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

Why the double backticks here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hm, I didn't notice it. Emacs markdown mode...

An ``axml/xml`` file that you want to associate with an activity needs to be modified to
include a few extra xml attributes on the root layout element.

Additionally, **only*** elements which have the `android:id` attribute will be accessible via
Copy link
Contributor

Choose a reason for hiding this comment

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

Too many -- or not enough -- *s. Presumably you want ***only***.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ditto here


class myScrollView_Class
{
public ScrollView Widget {
Copy link
Contributor

Choose a reason for hiding this comment

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

In terms of keeping code short for clarity reasons, we should go with:

public ScrollView Widget {get;}

for the following:

1. Android allows duplicate `android:id` values for **sibling** elements
2. Android allows duplicate `android:id` values anywhere withn the layout tree
Copy link
Contributor

Choose a reason for hiding this comment

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

Spelling error: you want within.

3. Many layouts reuse XML in the form of fragments
4. Many layouts reuse XML in the form of includes (using the `<include>` element)

`1.` means that there's direct access (via `FindViewById` or with code-behind) to the **first** element with that `id` **only**.
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of enclosing numbers in backticks, please surround with parens, e.g. (1), (2), etc.

// The actual view is set/replaced in `TestSuiteActivity.OnCreate()`
SetContentView (Resource.Layout.Main);

first_text_view.Widget.Click += delegate {
Copy link
Contributor

Choose a reason for hiding this comment

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

Question: Main.axml contains no <include/>s -- which aren't supported yet, anyway -- and only one element with android:id="@ +id/first_text_view".

Why isn't this a leaf node? Why do we need the extra .Widget here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's a bug. first_text_view should be a leaf element, but not because it's a single one with this particular id but because it doesn't have any child elements with android:id

@grendello grendello force-pushed the code-behind-fixes branch 4 times, most recently from fad217c to 6700103 Compare March 8, 2018 20:16
@grendello
Copy link
Contributor Author

build

continue;
}

Log.LogDebugMessage ($"Inner subtree: element == {subtree.LocalName}");
Copy link
Contributor

Choose a reason for hiding this comment

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

nice :D probably want to remote the debugging info

@grendello grendello force-pushed the code-behind-fixes branch 3 times, most recently from 568099e to 254627a Compare March 9, 2018 10:20
Fragment handling code partially taken from dotnet#1302

Fragments are treated as normal widget, with the difference that they have to be
found using `FragmentManager.FindFragmentById` instead of the usual
`Activity.FindViewById` method.

Each generated class also gets a property named `Widget` which returns the
actual Android widget as found in the layout file. This allows us to keep
hierarchical nature of the XML code (thus handling nested widgets with duplicate
ids gracefully) while being able to access the parent widget itself.

The commit introduces a new attribute called `tools:managedType` which is used
to specify the element associated property's type. We cannot use `tools:class`
for this purpose since the layout root element already uses it to specify the
name of the generated code-behind class and if the element had `android:id` on
it, we would end up using the activity's type for the root element's property in
the generated code, instead of its actual type.
@grendello grendello force-pushed the code-behind-fixes branch from 254627a to 2229509 Compare March 9, 2018 10:23
@jonpryor jonpryor merged commit d09b86a into dotnet:master Mar 9, 2018
jonpryor pushed a commit that referenced this pull request Mar 9, 2018
)

Fragment handling code partially taken from:

	#1302

Fragments are treated as normal widget, with the difference that they
have to be found using `FragmentManager.FindFragmentById()` instead
of the usual `Activity.FindViewById()` method.

Each generated class also gets a property named `Widget` which
returns the actual Android widget as found in the layout file. This
allows us to keep hierarchical nature of the XML code (thus handling
nested widgets with duplicate ids gracefully) while being able to
access the parent widget itself.

The commit introduces a new attribute called `tools:managedType`
which is used to specify the element associated property's type. We
cannot use `tools:class` for this purpose since the layout root
element already uses it to specify the name of the generated
code-behind class and if the element had `android:id` on it, we would
end up using the activity's type for the root element's property in
the generated code, instead of its actual type.
@grendello grendello deleted the code-behind-fixes branch March 9, 2018 16:44
@github-actions github-actions bot locked and limited conversation to collaborators Feb 2, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants