xamarin / Xamarin.Forms

Xamarin.Forms is no longer supported. Migrate your apps to .NET MAUI.
https://aka.ms/xamarin-upgrade
Other
5.62k stars 1.87k forks source link

[Bug] Updating Embedded Font #11843

Closed AlleSchonWeg closed 2 years ago

AlleSchonWeg commented 4 years ago

Description

If you embed a font via [assembly: ExportFont("fontfile.ttf", Alias = "IconFont")] all works correctly. But if you you update the fontfile in your project, add a new glyph for example, then the new glyph is not shown. Only the rectangle placeholder. I only tested with android, but perhaps this problem exists also in ios.

Expected Behavior

New glyph in updated fontfile should be available.

Actual Behavior

Label show only the rectangle placeholder.

Basic Information

Screenshots

Reproduction Link

Workaround

On Android delete AppData.

AlleSchonWeg commented 4 years ago

Somebody knows any better workaround? Because deleting appdata is no good idea if you releasing an app.

solomonfried commented 3 years ago

Is there any known workaround?

Your issue has been open since August. It appears in Triage with “Needs Estimate”.

Have you noticed if the issue occurs with apk bundles? I have been releasing aab. I know that the new font file is in the bundle because it gets installed if the previous version is removed first.

I was hoping there was a way to flag a resource as "always install"

Thanks

AlleSchonWeg commented 3 years ago

@samhouts , @rachelkang Is it possible to prioritise this issue? Because it blocks us from releasing our app.

solomonfried commented 3 years ago

There is a solution to this issue. This worked for me,

The Shared Assembly (where the font resources are included) needs to be added to the Forms.Init call in App.xaml.cs in the UWP project.

Assuming your app is called MyApp and you have projects something like this..

MyApp MyApp.iOS MyApp.Android MyApp.UWP (Universal Windows)

Edit App.xaml.cs in MyApp.UWP

In the OnLaunched method, before Forms.Init, add the following....

protected override void OnLaunched(LaunchActivatedEventArgs e)
 {
            var assembliesToInclude = new List<Assembly>
            {
                // .. whatever assemblies you may have now for adding 3rd party controls..

                // Add the Assembly of your applications Shared App.
                 typeof(MyApp.App).GetTypeInfo().Assembly
             }

              Forms.Init(e, assembliesToInclude);

}

Your ExportFont should work like it does on Android and iOS

AlleSchonWeg commented 3 years ago

Hi @solomonfried ,

this did not work for my with android (I don't use UWP). I used this:

     protected override void OnCreate(Bundle savedInstanceState)
        {
            ....
            global::Xamarin.Forms.Forms.Init(this, savedInstanceState, typeof(xxx.App).GetTypeInfo().Assembly);
            LoadApplication(new App());
        }

and a colleague created a new PR with an additional icon in our custom font. The new icon is not shown. Only reinstalling the app works. Then the icon appears.

solomonfried commented 3 years ago

Hi @solomonfried ,

this did not work for my with android (I don't use UWP). I used this:

  protected override void OnCreate(Bundle savedInstanceState)
     {
      ....
      global::Xamarin.Forms.Forms.Init(this, savedInstanceState, typeof(xxx.App).GetTypeInfo().Assembly);
      LoadApplication(new App());
  }

and a colleague created a new PR with an additional icon in our custom font. The new icon is not shown. Only reinstalling the app works. Then the icon appears.

My apologies. I confused 2 separate ExportFont issues that I was following.

For the issue of having to remove and reinstall the app in order to show glyph changes, have you tried renaming the FontFile? (e.g. add a number to the end) Then change the [ExportFont] attribute to reflect the change. I would assume that that would have to create a new Embedded Resource which should resolve the issue.

This is not a long term solution, but might get you through releasing your app now.

AlleSchonWeg commented 3 years ago

Hi @solomonfried , this did not work for my with android (I don't use UWP). I used this:

  protected override void OnCreate(Bundle savedInstanceState)
     {
        ....
        global::Xamarin.Forms.Forms.Init(this, savedInstanceState, typeof(xxx.App).GetTypeInfo().Assembly);
        LoadApplication(new App());
    }

and a colleague created a new PR with an additional icon in our custom font. The new icon is not shown. Only reinstalling the app works. Then the icon appears.

My apologies. I confused 2 separate ExportFont issues that I was following.

For the issue of having to remove and reinstall the app in order to show glyph changes, have you tried renaming the FontFile? (e.g. add a number to the end) Then change the [ExportFont] attribute to reflect the change. I would assume that that would have to create a new Embedded Resource which should resolve the issue.

This is not a long term solution, but might get you through releasing your app now.

Thanks. Renaming the updated fontfile works. I changed the name to fontfile_1.ttf and the attribute to [assembly: ExportFont("fontfile_1.ttf", Alias = "IconFont")]. No need to change the alias.

tringert commented 3 years ago

Thank you @solomonfried you saved my day! Renaming was the solution!

ChristopherStephan commented 3 years ago

Another workaround is to include the font files in the platform projects and mark them as AndroidAsset/BundleResource.

juwens commented 3 years ago

I developed this:

public static void DeleteFontCacheIfFontChanged()
{
    var assembly = typeof(App).Assembly;
    var exportFontAttribute = assembly.GetCustomAttributes(typeof(ExportFontAttribute), true).FirstOrDefault() as ExportFontAttribute;

    if (exportFontAttribute == null) return;

    string? fontFilePath = null;
    if (Device.RuntimePlatform == Device.Android)
    {
        fontFilePath = Path.Combine(Xamarin.Essentials.FileSystem.CacheDirectory, exportFontAttribute.FontFileName);
    }
    else if (Device.RuntimePlatform == Device.UWP)
    {
        fontFilePath = Path.Combine(Xamarin.Essentials.FileSystem.AppDataDirectory, "fonts", exportFontAttribute.FontFileName);
    }
    else if (Device.RuntimePlatform == Device.iOS)
    {
        // TODO
    }

    if (fontFilePath == null || !File.Exists(fontFilePath)) return;

    var deleteFile = false;

    var asmName = assembly.GetName().Name;
    using (var embeddedStream = assembly.GetManifestResourceStream(asmName + ".Resources.Fonts." + exportFontAttribute.FontFileName))
    using (var fileStream = File.OpenRead(fontFilePath))
    {
        var embeddedFontHash = GetHash(embeddedStream);
        var cachedFontHash = GetHash(fileStream);

        deleteFile = !embeddedFontHash.SequenceEqual(cachedFontHash);
    }

    if (deleteFile)
    {
        Debug.WriteLine($"deleting '{fontFilePath}'");
        File.Delete(fontFilePath);
    }

    static byte[] GetHash(Stream stream)
    {
        using (SHA256 hashAlgorithm = SHA256.Create())
        {
            byte[] data = hashAlgorithm.ComputeHash(stream);
            return data;
        }
    }
}
juwens commented 3 years ago

Any idea how I can add iOS to my code? I didn't find the file on the disk

Seuleuzeuh commented 3 years ago

Thanks @juwens for the workaround, it work greats (for an Android App).

I've adapted it to work with multiple EmbeddedFont.

zain442 commented 2 years ago

juwens where did you add that code?