Skip to content
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

[Xamarin.Android.Build.Tasks] Fragments & CodeBehind #1302

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Mono.Android/Test/Mono.Android-Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@
<AndroidResource Include="Resources\drawable\android_focused.png" />
<AndroidResource Include="Resources\drawable\android_normal.png" />
<AndroidResource Include="Resources\drawable\android_button.xml" />
<AndroidResource Include="Resources\layout\FragmentFixup.axml" />
<AndroidResource Include="Resources\layout\Main.axml" />
<AndroidResource Include="Resources\xml\XmlReaderResourceParser.xml" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
Expand Down
31 changes: 31 additions & 0 deletions src/Mono.Android/Test/Resources/layout/FragmentFixup.axml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<fragment
android:name="Xamarin.Android.RuntimeTests.MyFragment"
android:id="@+id/csharp_simple_fragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<fragment
android:name="xamarin.android.runtimetests.MyFragment"
android:id="@+id/csharp_legacy_fragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<fragment
android:name="Xamarin.Android.RuntimeTests.MyFragment, Mono.Android-Tests"
android:id="@+id/csharp_partial_assembly"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<fragment
android:name="Xamarin.Android.RuntimeTests.MyFragment, Mono.Android-Tests, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
android:id="@+id/csharp_full_assembly"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
34 changes: 28 additions & 6 deletions src/Mono.Android/Test/Resources/layout/Main.axml
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,37 @@
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
xmlns:tools="http://schemas.xamarin.com/android/tools"
tools:class="Xamarin.Android.RuntimeTests.MainActivity"
>
<TextView
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:id="@+id/first_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:editable="false"
android:background="@color/uicolor_lightGrayColor"
/>
/>
<TextView
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:id="@+id/second_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:editable="false"
android:background="@color/WhiterShadeOfPale"
/>
/>
<fragment
android:name="Xamarin.Android.RuntimeTests.MyFragment"
android:id="@+id/csharp_simple_fragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<fragment
android:name="Xamarin.Android.RuntimeTests.MyFragment, Hello"
android:id="@+id/csharp_partial_assembly"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<fragment
android:name="Xamarin.Android.RuntimeTests.MyFragment, Hello, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
android:id="@+id/csharp_full_assembly"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
using System.Reflection;
using Android.App;
using Android.OS;
using Android.Views;
using Android.Widget;
using Xamarin.Android.NUnitLite;

namespace Xamarin.Android.RuntimeTests
{
[Activity (Label = "runtime", MainLauncher = true,
Name="xamarin.android.runtimetests.MainActivity")]
public class MainActivity : TestSuiteActivity
public partial class MainActivity : TestSuiteActivity
{
protected override void OnCreate (Bundle bundle)
{
// Note; for testing <fragment/> fixup only.
// The actual view is set/replaced in `TestSuiteActivity.OnCreate()`
SetContentView (Resource.Layout.FragmentFixup);

first_text_view.Click += delegate {
// ignore
};

// tests can be inside the main assembly
AddTest (Assembly.GetExecutingAssembly ());
// or in any reference assemblies
Expand All @@ -20,5 +30,17 @@ protected override void OnCreate (Bundle bundle)
base.OnCreate (bundle);
}
}

#if __ANDROID_11__
public class MyFragment : Fragment {

public override View OnCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
return new TextView (Activity) {
Text = "via fragment!",
};
}
}
#endif // ANDROID_15
}

120 changes: 109 additions & 11 deletions src/Xamarin.Android.Build.Tasks/Tasks/GenerateLayoutCodeBehind.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public bool IsLeaf {
public Widget Parent { get; set; }
public bool IsInaccessible { get; set; }
public bool IsRoot { get; set; }
public bool IsFragment { get; set; }

public void AddChild (Widget child)
{
Expand Down Expand Up @@ -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 ()) {
line = column = 1;
return;
}
Expand Down Expand Up @@ -167,7 +168,6 @@ bool GenerateLayoutMembers (CodeTypeDeclaration mainClass, string fileName)
LoadWidgets (fileName, subtree, lineInfo, androidNS, root, globalIdCache);
}
}

}
}
}
Expand All @@ -194,8 +194,30 @@ void LoadWidgets (string fileName, XmlReader reader, IXmlLineInfo lineinfo, str
}
}
while (reader.Read ()) {
LoadWidgets (fileName, reader, lineinfo, androidNS, root ?? widgetRoot, globalIdCache);
LoadWidgets (fileName, reader, lineinfo, androidNS, widgetRoot, globalIdCache);
}
}

static readonly Dictionary<string, Func<Widget, XmlReader, Widget>> WidgetCreators = new Dictionary<string, Func<Widget, XmlReader, Widget>> {
Copy link
Contributor

Choose a reason for hiding this comment

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

A small nit - create the dictionary with StringComparer.Ordinal, it's going to be faster

["fragment"] = CreateFragmentWidget,
};

const string AndroidXmlNamespace = "http://schemas.android.com/apk/res/android";
const string AndroidXmlName = "name";

static Widget CreateFragmentWidget (Widget w, XmlReader e)
{
var type = e.GetAttribute (AndroidXmlName, AndroidXmlNamespace);
if (type == null) {
return null;
}

w.IsFragment = true;

var c = type.IndexOf (',');
w.Type = c < 0 ? type : type.Substring (0, c);
w.Type = w.Type;
return w;
}

Widget CreateWidget (string fileName, XmlReader e, IXmlLineInfo lineInfo, string id, Widget parent)
Expand All @@ -220,6 +242,11 @@ Widget CreateWidget (string fileName, XmlReader e, IXmlLineInfo lineInfo, string
Column = column
};

Func<Widget, XmlReader, Widget> creator;
if (WidgetCreators.TryGetValue (e.LocalName, out creator)) {
return creator (ret, e);
}

return ret;
}

Expand Down Expand Up @@ -401,7 +428,7 @@ CodeMemberMethod ImplementWidgetCreator (Widget widget, CodeExpression parent, L

CodeMethodInvokeExpression CreateFindViewInvoke (Widget widget, CodeExpression parent, CodeExpression parentView)
{
var findViewRef = new CodeMethodReferenceExpression (parent, "__FindView");
var findViewRef = new CodeMethodReferenceExpression (parent, widget.IsFragment ? "__FindFragment" : "__FindView");
findViewRef.TypeArguments.Add (new CodeTypeReference (widget.Type));

return new CodeMethodInvokeExpression (findViewRef, new CodeExpression [] { parentView, new CodeSnippetExpression (widget.ID) });
Expand All @@ -416,7 +443,7 @@ CodeMemberField CreateBackingField (Widget widget, string memberType)

CodeMemberField CreateBackingFuncField (Widget widget, string memberType)
{
return new CodeMemberField ($"Func<{memberType}>", $"__{widget.Name}Func");
return new CodeMemberField ($"global::System.Func<{memberType}>", $"__{widget.Name}Func");
}

bool HasUniqueId (Widget widget, Dictionary<string, int> globalIdCache)
Expand Down Expand Up @@ -484,7 +511,7 @@ ITaskItem GenerateCodeBehind (ITaskItem layoutFile, CodeGeneratorOptions generat
var ns = new CodeNamespace (namespaceName);
compileUnit.Namespaces.Add (ns);
foreach (string import in StandardImports)
ns.Imports.Add (new CodeNamespaceImport (import));
ns.Imports.Add (new CodeNamespaceImport ($"global::{import}"));

CodeTypeDeclaration mainClass = AddMainClass (layoutFile, ns, className);
if (!GenerateLayoutMembers (mainClass, Path.GetFullPath (layoutFile.ItemSpec)))
Expand Down Expand Up @@ -578,7 +605,9 @@ void AddCommonMembers (CodeTypeDeclaration klass, ITaskItem layoutFile)
klass.Members.Add (ImplementFindView (activityTypeRef));
klass.Members.Add (ImplementFindView (new CodeTypeReference ("Android.App.Fragment", CodeTypeReferenceOptions.GlobalReference), activityTypeRef, (CodeVariableReferenceExpression parentView) => new CodePropertyReferenceExpression (parentView, "Activity")));
klass.Members.Add (ImplementEnsureView ());
klass.Members.Add (ImplementFindFragment (activityTypeRef));
klass.Members.Add (new CodeSnippetTypeMember ("\tpartial void OnLayoutViewNotFound<T> (int resourceId, ref T type) where T : global::Android.Views.View;"));
klass.Members.Add (new CodeSnippetTypeMember ("\tpartial void OnLayoutFragmentNotFound<T> (int resourceId, ref T type) where T : global::Android.App.Fragment;"));
}

CodeMemberMethod ImplementInitializeContentView (ITaskItem layoutFile)
Expand Down Expand Up @@ -609,7 +638,7 @@ CodeMemberMethod ImplementEnsureView ()
method.TypeParameters.Add (typeParam);

var tRef = new CodeTypeReference (typeParam);
var funcRef = new CodeTypeReference (typeof (Func<>));
var funcRef = new CodeTypeReference (typeof (Func<>), CodeTypeReferenceOptions.GlobalReference);
funcRef.TypeArguments.Add (tRef);
method.Parameters.Add (new CodeParameterDeclarationExpression (funcRef, "creator"));

Expand All @@ -635,7 +664,7 @@ CodeMemberMethod ImplementEnsureView ()
var creatorVarRef = new CodeVariableReferenceExpression ("creator");
var argNullEx = new CodeThrowExceptionStatement (
new CodeObjectCreateExpression (
new CodeTypeReference (typeof (ArgumentNullException)),
new CodeTypeReference (typeof (ArgumentNullException), CodeTypeReferenceOptions.GlobalReference),
new [] { new CodeSnippetExpression ("nameof (creator)") }
)
);
Expand All @@ -653,6 +682,75 @@ CodeMemberMethod ImplementEnsureView ()
return method;
}

CodeMemberMethod ImplementFindFragment (CodeTypeReference typeForParent, CodeTypeReference typeForOverloadCall = null, Func<CodeVariableReferenceExpression, CodeExpression> constructParentViewCall = null)
{
CodeMemberMethod method = CreateMethod ("__FindFragment", MethodAccessibility.Private, MethodScope.Final);

var typeParam = new CodeTypeParameter ("T") {
Constraints = {
new CodeTypeReference ("Android.App.Fragment", CodeTypeReferenceOptions.GlobalReference),
},
};
method.TypeParameters.Add (typeParam);
method.Parameters.Add (new CodeParameterDeclarationExpression (typeForParent, "activity"));
method.Parameters.Add (new CodeParameterDeclarationExpression (typeof (int), "id"));

var tReference = new CodeTypeReference (typeParam);
method.ReturnType = tReference;

// T fragment = FragmentManager.FindFragmentById<T> (id);
var id = new CodeVariableReferenceExpression ("id");

var findByIdRef = new CodeMethodReferenceExpression (
new CodePropertyReferenceExpression (new CodeVariableReferenceExpression ("activity"), "FragmentManager"),
"FindFragmentById",
new [] { tReference }
);

var findByIdInvoke = new CodeMethodInvokeExpression (findByIdRef, new [] { id });
var viewVar = new CodeVariableDeclarationStatement (tReference, "fragment", findByIdInvoke);
method.Statements.Add (viewVar);

// if (view == null) {
// OnLayoutFragmentNotFound (resourceId, ref view);
// }
// if (view != null)
// return view;
// throw new System.InvalidOperationException($"Fragment not found (ID: {id})");

var viewVarRef = new CodeVariableReferenceExpression ("fragment");
var ifViewNotNull = new CodeConditionStatement (
new CodeBinaryOperatorExpression (viewVarRef, CodeBinaryOperatorType.IdentityInequality, new CodePrimitiveExpression (null)),
new CodeStatement [] { new CodeMethodReturnStatement (viewVarRef) }
);

var viewRefParam = new CodeDirectionExpression (FieldDirection.Ref, viewVarRef);
var viewNotFoundInvoke = new CodeMethodInvokeExpression (
new CodeThisReferenceExpression (),
"OnLayoutFragmentNotFound",
new CodeExpression [] { id, viewRefParam }
);

var ifViewNull = new CodeConditionStatement (
new CodeBinaryOperatorExpression (viewVarRef, CodeBinaryOperatorType.IdentityEquality, new CodePrimitiveExpression (null)),
new CodeStatement [] { new CodeExpressionStatement (viewNotFoundInvoke) }
);

method.Statements.Add (ifViewNull);
method.Statements.Add (ifViewNotNull);

var throwInvOp = new CodeThrowExceptionStatement (
new CodeObjectCreateExpression (
new CodeTypeReference (typeof (InvalidOperationException), CodeTypeReferenceOptions.GlobalReference),
new [] { new CodeSnippetExpression ("$\"Fragment not found (ID: {id})\"") }
)
);

method.Statements.Add (throwInvOp);

return method;
}

CodeMemberMethod ImplementFindView (CodeTypeReference typeForParent, CodeTypeReference typeForOverloadCall = null, Func<CodeVariableReferenceExpression, CodeExpression> constructParentViewCall = null)
{
CodeMemberMethod method = CreateMethod ("__FindView", MethodAccessibility.Private, MethodScope.Final);
Expand Down Expand Up @@ -729,7 +827,7 @@ CodeMemberMethod ImplementFindView (CodeTypeReference typeForParent, CodeTypeRef

var throwInvOp = new CodeThrowExceptionStatement (
new CodeObjectCreateExpression (
new CodeTypeReference (typeof (InvalidOperationException)),
new CodeTypeReference (typeof (InvalidOperationException), CodeTypeReferenceOptions.GlobalReference),
new [] { new CodeSnippetExpression ("$\"View not found (ID: {resourceId})\"") }
)
);
Expand All @@ -746,7 +844,7 @@ CodeMemberMethod CreateMethod (string methodName, MethodAccessibility access, Me

CodeMemberMethod CreateMethod (string methodName, MethodAccessibility access, MethodScope scope, Type returnType)
{
return CreateMethod (methodName, access, scope, new CodeTypeReference (returnType));
return CreateMethod (methodName, access, scope, new CodeTypeReference (returnType, CodeTypeReferenceOptions.GlobalReference));
}

CodeMemberMethod CreateMethod (string methodName, MethodAccessibility access, MethodScope scope, string returnType)
Expand Down Expand Up @@ -813,7 +911,7 @@ void MarkAsCompilerGenerated (CodeTypeMember member)

void AddCustomAttribute (CodeAttributeDeclarationCollection attributes, Type type)
{
attributes.Add (new CodeAttributeDeclaration (new CodeTypeReference (type)));
attributes.Add (new CodeAttributeDeclaration (new CodeTypeReference (type, CodeTypeReferenceOptions.GlobalReference)));
}
}
}