Converter not working with linking enabled.

Hello everyone,

I've been recently trying to shrink the size of the apk of a xamarin.forms application with the linker. Option set: SDK and User Assemblies.

Using this option caused some of the code (specially constructors) used in runtime to be stripped, but I managed to trick the linker into keeping them by instantiating such classes on the MainActivity of the app.

However, I use a Converter on a ListView Item Template (xaml) to convert a boolean value to a color. When debugging the converter simply does not work and doesn't throw any exceptions.

Any idea on how to tackle this issue? Or maybe somehow debug and find out why the converters are not working?

This is what the converter looks like:

public class BoolToAlarmColorConverter : IValueConverter
    {        
        public string OK_GREEN = "#00ff00";
        public string WARNING_RED = "#ff0000";

        public string OK_LIME = "#4cd964";
        public string WARNING_PINK = "#ff2d55";

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value.Equals(true) ? WARNING_RED : OK_LIME;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

Thanks in advance

Best Answers

  • AlessandroCaliaroAlessandroCaliaro IT ✭✭✭✭✭
    Accepted Answer

    I don't understand why you are complicating your life... In all my projects I have Link SDK assemblies only and I have no problems.

    Also debug works fine with Link SDK assemblies only. If you take a look to

                if (measurementsFromApi != null)                           
                listView.ItemsSource = measurementsFromApi;
    

    and watch

    measurementsFromApi[0].Alarmado
    

    it does not appears when you change to Link all assemblies

    I suggest to use INotifyPropertyChanged and ObservableCollections when you works with Binding and ListView

    It seems that you can use [Preserve] when you want to preserve some class or properties

    But I say, you are complicating your life.

    ps. Take a look to the attachment, does it works for you? https://dropbox.com/s/t1srog9e3tnxo3b/LinkingConverterExample.zip?dl=0

  • Accepted Answer

    That line gave me an idea and it worked!

    System.Diagnostics.Debug.WriteLine(measurementsFromApi[0].Alarmado);

    "getting" the alarmado property prevented the linker from stripping away something (probably the property's getter) such that it worked on Debug mode.

    However, setting it to release stripped the diagnostics line that used the getter, and as a consequence stripped the getter.

    THE SOLUTION

    Just "get" the property somewhere in the code. In my case I simply added a dummy get to the Alarmado property of an instance of Measurement on LinkerInclude.cs like so:

    public LinkerInclude()
            {
                new Plugin.Toasts.ToastNotification();
                new Measurement();
                Measurement m = new Measurement("CTSE4983590", "10/01/2017 - 12:50:11", 20.2f, 12.1f, 30f, true);
    
                bool dummy = m.Alarmado;                                
            }
     }
    

    And it worked perfectly. I didn't change anything else from the original code. Didn't use Fody or ObservableCollections

    Thanks again @AlessandroCaliaro

Answers

  • AlessandroCaliaroAlessandroCaliaro ITMember ✭✭✭✭✭

    I add some check

    if(value != null && value is bool){
        if((bool) value == true) return WARNING_RED; else return OK_LIME;
    }
    
  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    I use a Converter on a ListView Item Template (xaml) to convert a boolean value to a color.

    That converter doesn't return a Color. It returns a String that happens to hold the hex representation of a Color.

    Depending on how and where you're using it, the user might not get what it expects.

    You could also avoid the converter completely if you use a DataTrigger in your XAML to change the color based on whatever property you are sending to this converter.

  • AlessandroCaliaroAlessandroCaliaro ITMember ✭✭✭✭✭

    @ClintStLaurent is right

    you should use something like

    Color.FromHex(WARNING_RED);
    
  • edited March 2017

    @AlessandroCaliaro said:
    @ClintStLaurent is right

    you should use something like

    Color.FromHex(WARNING_RED);

    You are both correct that it is returning a string.. but that is how I use it in my xaml. In fact, the app works correctly and displays the correct colors if I'm using "Sdk Assemblies Only". The problem only appears if the option is set to SDK and User Assemblies.

    Anyway, here is the xaml:

    <StackLayout Orientation="Horizontal"
            HorizontalOptions="FillAndExpand"
            Padding="5"
            BackgroundColor="{Binding Alarmado,Converter={StaticResource BoolToAlarmColorConverter}}">
                    <Image Source="{Binding Alarmado,Converter={StaticResource BoolToAlarmImageConverter}}" 
                              HeightRequest="40"
                              WidthRequest="40"
                              VerticalOptions="FillAndExpand" 
                              HorizontalOptions="FillAndExpand" />
    </StackLayout>
    

    The BoolToAlarmImageConverter is also suffering from the same problem btw.

  • Is there an alternative to using converter at least?

  • AlessandroCaliaroAlessandroCaliaro ITMember ✭✭✭✭✭

    Converter is not a problem. there is something else that is not correct.
    Post a REPO on GITHUB with your problem and I take a look

  • @AlessandroCaliaro said:
    Converter is not a problem. there is something else that is not correct.
    Post a REPO on GITHUB with your problem and I take a look

    Ok, here it is.
    The .Droid project is set to Sdk Assemblies Only. Run it like that if you wish to see the correct behaviour and then just change it to Sdk and User Assemblies to see the misbehaviour. Don't forget to clean it after changing the property as sometimes it the previous setting persists.

    I've also changed it a bit to work offline but that probably won't be an issue.

    Thank you very much in advance.

    Ps.: the page in question is not the first screen that appears after the loading screen, so please press the (only) button with "Acessar" written on it.

    @ClintStLaurent said:
    You could also avoid the converter completely if you use a DataTrigger in your XAML to change the color based on whatever property you are sending to this converter.

    I will check out DataTrigger as well.

  • edited March 2017

    @AlessandroCaliaro said:
    Converter is not a problem. there is something else that is not correct.
    Post a REPO on GITHUB with your problem and I take a look

    Ok, here it is. I had to modify it a bit for legal reasons and so that it would work offline but that shouldn't be a problem.
    Note: the .droid project is set to Sdk Assemblies Only so that the correct behavior is evident. Change to Sdk and User Assemblies to see the problem happening.

    Thanks in advance

  • AlessandroCaliaroAlessandroCaliaro ITMember ✭✭✭✭✭

    I am a little confused...

    what is this code in you Android prj

    class LinkerInclude
    {
        public LinkerInclude()
        {
            new Plugin.Toasts.ToastNotification();
            new Measurement();
            new Measurement("CTSE4983590", "10/01/2017 - 12:50:11", 20.2f, 12.1f, 30f, true);
    
    
            //new BoolToAlarmImageConverter();
            //new BoolToAlarmColorConverter();
        }
    }
    
  • edited March 2017

    @AlessandroCaliaro
    That code is a "technique" I've read that tricks the linker to not remove code that is actually used in runtime.

    Let's say I comment out the new Plugin.Toasts.ToastNotification(); When the app runs and I open a page that uses it, the runtime throws a System.MissingMethodException: Default constructor not found for type Toasts.Forms.Plugin.Droid.ToastNotification exception. By adding this instantiation, we trick the linker to keep this necessary code.

    yeah...I know....

    The other two lines:

    new Measurement();
    new Measurement("CTSE4983590", "10/01/2017 - 12:50:11", 20.2f, 12.1f, 30f, true);
    

    serve the same purpose. Preserve code that would otherwise be stripped.

    The two commented lines:

    //new BoolToAlarmImageConverter();
    //new BoolToAlarmColorConverter();
    

    were an attempt to do the same with the converters, as I suspected that it had something to do with the linker. But it had no effect adding them, so I commented them out.

  • edited March 2017

    Ok, an update. On running the application I noticed this on the console:

    Binding: 'Alarmado' property not found on 'RCS0500Mobile.Model.Measurement', target property: 'Xamarin.Forms.StackLayout.BackgroundColor'

    and

    Binding: 'Alarmado' property not found on 'RCS0500Mobile.Model.Measurement', target property: 'Xamarin.Forms.Image.Source'

    The Alarmado bool property is precisely the property I am using on the converters.

    The Linker is stripping the Alarmado property from my Measurement class.

    Any suggestions on keeping this property intact are welcome.

  • AlessandroCaliaroAlessandroCaliaro ITMember ✭✭✭✭✭
    Accepted Answer

    I don't understand why you are complicating your life... In all my projects I have Link SDK assemblies only and I have no problems.

    Also debug works fine with Link SDK assemblies only. If you take a look to

                if (measurementsFromApi != null)                           
                listView.ItemsSource = measurementsFromApi;
    

    and watch

    measurementsFromApi[0].Alarmado
    

    it does not appears when you change to Link all assemblies

    I suggest to use INotifyPropertyChanged and ObservableCollections when you works with Binding and ListView

    It seems that you can use [Preserve] when you want to preserve some class or properties

    But I say, you are complicating your life.

    ps. Take a look to the attachment, does it works for you? https://dropbox.com/s/t1srog9e3tnxo3b/LinkingConverterExample.zip?dl=0

  • @AlessandroCaliaro said:
    ps. Take a look to the attachment, does it works for you? https://dropbox.com/s/t1srog9e3tnxo3b/LinkingConverterExample.zip?dl=0

    I'm still studying what you suggested and the solution. (INotifyPropertyChanged and ObservableCollections)
    Thank you so much for your time. I'd pay a beer if I could because this seems to be working perfectly!

    I've also noticed Fody package was added. Will look into it as well.

    @AlessandroCaliaro said:
    I don't understand why you are complicating your life... In all my projects I have Link SDK assemblies only and I have no problems.

    Also debug works fine with Link SDK assemblies only. If you take a look to

    So, I know that it works with Sdk Assemblies Only, but the reason why I need to link both sdk and user assemblies (aka complicate my life) is that the app is too big with sdk assemblies only, 21Mb.

    After I solve this the next step will be to shrink using ProGuard...

    @AlessandroCaliaro said:
    ps. Take a look to the attachment, does it works for you? https://dropbox.com/s/t1srog9e3tnxo3b/LinkingConverterExample.zip?dl=0

  • AlessandroCaliaroAlessandroCaliaro ITMember ✭✭✭✭✭

    the strange thing is not ObservableCollection, Fody... (that you have to use) but

                System.Diagnostics.Debug.WriteLine(measurementsFromApi[0].Alarmado);
    

    in ContainerPage.xaml.cs

    try to remove it, the probles should appears again

  • Accepted Answer

    That line gave me an idea and it worked!

    System.Diagnostics.Debug.WriteLine(measurementsFromApi[0].Alarmado);

    "getting" the alarmado property prevented the linker from stripping away something (probably the property's getter) such that it worked on Debug mode.

    However, setting it to release stripped the diagnostics line that used the getter, and as a consequence stripped the getter.

    THE SOLUTION

    Just "get" the property somewhere in the code. In my case I simply added a dummy get to the Alarmado property of an instance of Measurement on LinkerInclude.cs like so:

    public LinkerInclude()
            {
                new Plugin.Toasts.ToastNotification();
                new Measurement();
                Measurement m = new Measurement("CTSE4983590", "10/01/2017 - 12:50:11", 20.2f, 12.1f, 30f, true);
    
                bool dummy = m.Alarmado;                                
            }
     }
    

    And it worked perfectly. I didn't change anything else from the original code. Didn't use Fody or ObservableCollections

    Thanks again @AlessandroCaliaro

  • N_BauaN_Baua INMember ✭✭✭✭✭

    Hi @MatheusFariadeAlencar.9424 ,

    Do you propose that we can have a dummy class like LinkerInclude.cs and then in constructor of such class just make some instance of the required plug-in(s)?- (even if not used) just to mimic that they are being used and so the linker would spook its mind for a while and we get all those plug-in in our resulting APK or IPA.

    If the answer to above is YES, including the LinkerIncude.cs in PCL is enough, I guess, correct me if I am wrong.

    I'll try this concept though, Thanks.

    Regards,
    N Baua

Sign In or Register to comment.