Context is obsolete as of version 2.5

ZenDroidZenDroid ✭✭✭USMember ✭✭✭

I updated my project to Xamarin forms 2.5. When I use Xamarin.Forms I get warning:

  Context is obsolete as of version 2.5. Please use a local context instead

How use local context instead?

«1

Posts

  • ChrisLarkinChrisLarkin ✭✭ USUniversity ✭✭

    +1 I would like to know this as well.

  • Angelru9Angelru9 ✭✭✭ ESMember ✭✭✭

    ummm, same issue here. how to solution it?

  • ChrisLarkinChrisLarkin ✭✭ USUniversity ✭✭
    edited November 2017

    What about in cases like this?

    Context context = Forms.Context;
    string baseName = App.ApplicationName;
    return baseName + context.PackageManager.GetPackageInfo(context.PackageName, 0).VersionName;
    
  • pedrofcjpedrofcj ✭✭ USMember ✭✭

    @ChrisLarkin said:
    What about in cases like this?

    Context context = Forms.Context;
    string baseName = App.ApplicationName;
    return baseName + context.PackageManager.GetPackageInfo(context.PackageName, 0).VersionName;
    

    In a case like this you can use Plugin.CurrentActivity (https://github.com/jamesmontemagno/Xamarin.Plugins) and change Forms.Context to CrossCurrentActivity.Current.Activity

  • KavalanKavalan ✭✭ NLMember ✭✭
    edited November 2017

    @ChrisLarkin said:
    What about in cases like this?

    Context context = Forms.Context;
    string baseName = App.ApplicationName;
    return baseName + context.PackageManager.GetPackageInfo(context.PackageName, 0).VersionName;
    
  • pedrofcjpedrofcj ✭✭ USMember ✭✭
    edited November 2017

    @nadjib said:
    In Xamarin.Forms, Forms.Context is always MainActivity. So you can create a static property in MainActivity and assign it in OnCreate():

    for eg:

    internal static MainActivity Instance { get; private set; }
    
    protected override void OnCreate(Bundle bundle) 
    {
      Instance = this;
    
      // Forms initialization here...
    }
    

    Then you can get it from anywhere inside your Android project

    var context = MainActivity.Instance;
    

    Done.

    Not always, when you render a native activity from XF this context will change.

    EDIT:
    Nevermind, I just tested it rendering an native activity from XF and it worked

  • ZenDroidZenDroid ✭✭✭ USMember ✭✭✭

    In my opinion this question is not clear enough. I would like to hear the comments of the Xamarin Team.

  • EZHartEZHart Xamurai USXamarin Team Xamurai

    In order to support Embedding in Android apps which use multiple Activities, we had to give everything the option of specifying the Context. Also, Forms.Context was a global static reference to an Activity which is ... icky for other reasons.

    The answers upthread have it right - for custom renderers, you just have to add the new constructor and everything will work fine. (TBH, if you never intend to embed your stuff in a native Android app with multiple Activities, you could just ignore the warning; it's all backward compatible. But again, that would be icky. Just add the constructor.)

    If you were using Forms.Context to do Context-related stuff in custom renderers, you probably should just be using the View's Context anyway. But for places where you legitimately need the current Context, Motz's plugin is a great option.

    I realize the custom renderer documentation is lagging behind on this subject - we'll try to get that fixed next week.

  • ZenDroidZenDroid ✭✭✭ USMember ✭✭✭

    Thank you. You explained this question.

  • SeQentLtdSeQentLtd ✭✭ CAUniversity ✭✭

    @BrandonMinnick

    @BrandonMinnick said:

    How to Update Custom Renderers

    Add the overloaded Constructor to each custom renderer

    Here is an example using a ButtonRenderer

    [assembly: ExportRenderer(typeof(CustomButton), typeof(CustomButtonRenderer))]
    namespace MyApp.Droid
    {
      public class CustomButtonRenderer : ButtonRenderer
      {
            public CustomButtonRenderer(Context context) : base(context)
            {
                
            }
    
          protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
          {
              base.OnElementPropertyChanged(sender, e);
              //ToDo: Customize Button
          }
      }
    }
    

    This is causing my application to crash Xamarin.Forms version 2.5.0.91635

  • ZenDroidZenDroid ✭✭✭ USMember ✭✭✭
    edited December 2017

    I also get this error:
    I added new constructor for my rendering

                protected MyTypeRender(Context context) : base(context)
        {
        }  
    

    I get exception:

       Constructor on type 'MyTypeRender' not found
    

    This is declaration my class:

        public class MyRender : ViewRenderer<MyCustom, AutoCompleteTextView>
    
  • JoeMankeJoeManke ✭✭✭✭✭ USMember ✭✭✭✭✭

    Make the constructor public.

  • ZenDroidZenDroid ✭✭✭ USMember ✭✭✭

    Thank you :)(my bug). It is working

  • antonduzenkoantonduzenko ✭✭ UAMember ✭✭

    I used to create dialogs like this
    AlertDialog.Builder b = new AlertDialog.Builder( Forms.Context );

    And it worked fine. But Forms.Context is obsolete now so I changed it to
    AlertDialog.Builder b = new AlertDialog.Builder( Android.App.Application.Context );

    Now it crashes with
    Android.Content.Res.Resources+NotFoundException: Resource ID #0x0

    I tried the alternative constructor
    AlertDialog.Builder b = new AlertDialog.Builder( Android.App.Application.Context, Resource.Style.Theme_AppCompat_Light );

    And that crashes with
    Android.Views.WindowManagerBadTokenException: Unable to add window -- token null is not for an application

    So what context do I pass to AlertDialog.Builder() in XF2.5? I don't feel like installing a 3rd party library for this.

  • JoeMankeJoeManke ✭✭✭✭✭ USMember ✭✭✭✭✭
    edited December 2017

    Where are you creating this dialog? If it's in a renderer, they still have a Context property. If it's an Android Service, then you can use this as Service is a subclass of Context. Otherwise you can pass a Context as a constructor argument when you create the class or use the CurrentActivity plugin.

  • JohnHindJohnHind ✭✭ GBMember ✭✭

    It would be useful if you could update this:
    https://developer.xamarin.com/guides/xamarin-forms/getting-started/hello-xamarin-forms/quickstart/
    It is not good when the getting started tutorial throws obsolete code warnings! How would you modify this (please, code not words)? Simple code like this should not require a third-party plugin!

  • EZHartEZHart Xamurai USXamarin Team Xamurai

    Updating the documentation and examples is in progress - some of that's running a bit slow because of the holidays.

    As for how to do this right now, one option would be setter injection:

    public class PhoneDialer : IDialer
    {
        internal Context Context { get; set; }
    
        public bool Dial(string number)
        {
            if (Context == null)
                return false;
            ...
        }
        ...
    }
    

    Then, in MainActivity:

    global::Xamarin.Forms.Forms.Init(this, bundle);
    
    var dialer = DependencyService.Get<IDialer>(DependencyFetchTarget.GlobalInstance);
    var androidDialer = dialer as PhoneDialer;
    androidDialer.Context = this; 
    
    LoadApplication(new App());
    

    Or you could provide the PhoneDialer class with a static delegate for retrieving the Context:

    public class PhoneDialer : IDialer
    {
        internal static Func<Context> GetContext { get; set; }
    
        public bool Dial(string number)
        {
            var context = GetContext();
    
            ...
        }
        ...
    }
    

    and in MainActivity, something like this:

    base.OnCreate(bundle);
    PhoneDialer.GetContext = () => this;
    global::Xamarin.Forms.Forms.Init(this, bundle);
    LoadApplication(new App());
    

    I'm certain there are other options, that's just what pops to mind.

    Regrettably, at the moment there's no way to register a specific instance of a class directly with the DependencyService; it relies on creating its own instances of classes using reflection. And there's no way to register, say, a method for creating/retrieving the dependency. In the future, we might add that capability in order to make situations like this one a little less awkward (e.g., giving users the ability to do something like var context = DependencyService.Get<IContextService>().Context;).

  • programmerhammerprogrammerhammer ✭✭ BYMember ✭✭

    First way is nice and simple. But as I have around 10 dependencies, seems it is better to wait for a final solution.

  • ShantimohanElchuriShantimohanElchuri ✭✭✭✭✭ USMember ✭✭✭✭✭
    edited January 2018

    @EZHart Is there anything wrong if I simply do as follows?

    Context context = Android.App.Application.Context;
    

    As of now this is working. Then why should I implement complex code?

  • EZHartEZHart Xamurai USXamarin Team Xamurai

    @ShantimohanElchuri said:
    @EZHart Is there anything wrong if I simply do as follows?

    Context context = Android.App.Application.Context;
    

    As of now this is working. Then why should I implement complex code?

    Short answer: if it's working, then go ahead and keep using the Application Context.

    Which context you use depends on which capabilities you need. The Application Context doesn't have all of the same capabilities as an Activity, and using the wrong one can cause subtle problems.

    This article is still a pretty good reference about the differences. For many situations, Application Context will be fine, and it has the advantage that you're unlikely to cause a memory leak with it.

    For the PhoneDialer example, I used Activity because the service is responsible for starting another Activity (the phone UI). You can (AFAIK) start an Activity from the Application Context, but that can result in weird stack issues. It's probably not an issue in your typical Forms app (especially since they usually only have a single Activity), but old habits die hard.

  • ShantimohanElchuriShantimohanElchuri ✭✭✭✭✭ USMember ✭✭✭✭✭

    @EZHart said:

    @ShantimohanElchuri said:
    @EZHart Is there anything wrong if I simply do as follows?

    Context context = Android.App.Application.Context;
    

    As of now this is working. Then why should I implement complex code?

    Short answer: if it's working, then go ahead and keep using the Application Context.

    Which context you use depends on which capabilities you need. The Application Context doesn't have all of the same capabilities as an Activity, and using the wrong one can cause subtle problems.

    This article is still a pretty good reference about the differences. For many situations, Application Context will be fine, and it has the advantage that you're unlikely to cause a memory leak with it.

    For the PhoneDialer example, I used Activity because the service is responsible for starting another Activity (the phone UI). You can (AFAIK) start an Activity from the Application Context, but that can result in weird stack issues. It's probably not an issue in your typical Forms app (especially since they usually only have a single Activity), but old habits die hard.

    ThanQ...for the detailed explanation.

    However, it appears your left mouse button is stuck :)

  • EZHartEZHart Xamurai USXamarin Team Xamurai

    @ShantimohanElchuri said:
    ThanQ...for the detailed explanation.

    However, it appears your left mouse button is stuck :)

    Ha! Deleted the extras, thanks.

  • JohnHindJohnHind ✭✭ GBMember ✭✭

    @EZHart said:
    Updating the documentation and examples is in progress - some of that's running a bit slow because of the holidays.

    As for how to do this right now, one option would be setter injection:

    public class PhoneDialer : IDialer
    {
      internal Context Context { get; set; }
    
      public bool Dial(string number)
      {
          if (Context == null)
              return false;
          ...
      }
      ...
    }
    

    Then, in MainActivity:

    global::Xamarin.Forms.Forms.Init(this, bundle);
    
    var dialer = DependencyService.Get<IDialer>(DependencyFetchTarget.GlobalInstance);
    var androidDialer = dialer as PhoneDialer;
    androidDialer.Context = this; 
    
    LoadApplication(new App());
    

    Or you could provide the PhoneDialer class with a static delegate for retrieving the Context:

    public class PhoneDialer : IDialer
    {
      internal static Func<Context> GetContext { get; set; }
    
      public bool Dial(string number)
      {
          var context = GetContext();
          
          ...
      }
      ...
    }
    

    and in MainActivity, something like this:

    base.OnCreate(bundle);
    PhoneDialer.GetContext = () => this;
    global::Xamarin.Forms.Forms.Init(this, bundle);
    LoadApplication(new App());
    

    The trouble with these solutions (and others offered) is that they break all reasonable standards of containment. The MainActivity should not have to know what platform specific implementations might be invoked by the generic services in the platform independent code. Also we need to be able to write portable code that can be consumed without having to inject support into user code (which is why I do not like using plugins if I can possibly avoid it).

    To give a concrete example, I have a BLE implementation which is mostly platform independent with an interface to a platform specific shim (like a custom renderer, but not). In the shim, I do something like:

    await ((JHMainActivity)Forms.Context).RunActivity(new Intent(BluetoothAdapter.ActionRequestEnable));

    JHMainActivity inherits from FormsAppCompatActivity and becomes the base for the applications MainActivity. It provides essential Activity related services for shims in a generic way. For example RunActivity does StartActivityForResult and returns a task which unblocks when the Result message is received. Ideally these essential services would be provided by Xamarin so they are done in a standard way and code is portable. But we also need a replacement for Forms.Context which does whatever it needs to do under the hood to correctly reference the current activity.

    In the mean time, can I continue to use Forms.Context safely provided I only ever create a single Activity?

  • EZHartEZHart Xamurai USXamarin Team Xamurai

    The MainActivity should not have to know what platform specific implementations might be invoked by the generic services in the platform independent code.

    I'm not sure I'm following you on this. Once you're dealing with MainActivity, you're already in platform-specific code. The examples above deal with configuring the platform-specific implementation of the service being used.

    To give a concrete example, I have a BLE implementation which is mostly platform independent with an interface to a platform specific shim (like a custom renderer, but not). In the shim, I do something like:

    await ((JHMainActivity)Forms.Context).RunActivity(new Intent(BluetoothAdapter.ActionRequestEnable));

    When you call Forms.Init(this, bundle) in your MainActivity, that's where Forms.Context is set. Forms.Context == MainActivity. So you're injecting MainActivity into the platform-specific part of your code. That's what the various PhoneDialer examples above are doing; they're just not doing it via Forms.Context.

    As a side note: I'm betting that in your situation, you'd be fine using the Application Context. Since BluetoothAdapter.ActionRequestEnable just launches a single "turn on BlueTooth" screen and then immediately returns to your application, you're not likely to run into any weird stack issues.

    But we also need a replacement for Forms.Context which does whatever it needs to do under the hood to correctly reference the current activity.

    Forms.Context never referenced "the current activity". It referenced the Activity which called Forms.Init. That's why it was deprecated - for applications which use multiple Activities, it's not safe to use Forms.Context because it might not be the current Activity.

    In the mean time, can I continue to use Forms.Context safely provided I only ever create a single Activity?

    You'll be fine for the foreseeable future.

  • DavidGerding.4136DavidGerding.4136 ✭✭ USMember ✭✭

    Hi All, Not sure if this will get seen, but I'm trying to update the constructor of a "straightforward" implementation of a HybridWebView using the process here.

    It worked fine but broke with 2.5. I've got the code as follows but it at compile time it says "Android.Webkit does not exist" even though I have the using for WebKit and it doesn't complain. Pretty perplexed.

    public class HybridWebViewRenderer2 : ViewRenderer<HybridWebView2, Android.Webkit.WebView>
        {
            const string JavaScriptFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
            Context _context;
    
            public HybridWebViewRenderer2(Context context) : base(context)
            {
                    _context = context;
            }
    ….
    

    Should I have that Context property? And/or do I need to set it/update somehow using CrossCurrent Activity referenced above instead?

    Any guidance appreciated.

    Dave G

  • JoeMankeJoeManke ✭✭✭✭✭ USMember ✭✭✭✭✭

    You do not need a separate _context, you inherit the Context property from the Android View base class.

  • Pierre-ChristopheDusPierre-ChristopheDus ✭✭✭ FRUniversity ✭✭✭
    edited February 2018

    There is something that I don't understand.

    Before this, I use a CustomRenderer like this:

    [assembly: ExportRenderer(typeof(CircleIconButton), typeof(CircleIconButtonRenderer))]
    namespace MyProject.Droid.Renderers
    {
    
        public class CircleIconButtonRenderer : ButtonRenderer
        {
            protected override void OnDraw(Android.Graphics.Canvas canvas)
            {
                base.OnDraw(canvas);
            }
            ...
        }
    }
    

    All worked fine: I was entering in the OnDraw() method.

    However with XF 2.5, I must do this:

    [assembly: ExportRenderer(typeof(CircleIconButton), typeof(CircleIconButtonRenderer))]
    namespace MyProject.Droid.Renderers
    {
    
        public class CircleIconButtonRenderer : ButtonRenderer
        {
    
            public CircleIconButtonRenderer(Context context) : base(context)
            {
    
            }
    
            protected override void OnDraw(Android.Graphics.Canvas canvas)
            {
                base.OnDraw(canvas);
            }
            ...
        }
    }
    

    This time it's not working: I don't enter in the OnDraw() method.

    Is this normal?

  • EZHartEZHart Xamurai USXamarin Team Xamurai

    @DavidGerding.4136 said:
    Hi All, Not sure if this will get seen, but I'm trying to update the constructor of a "straightforward" implementation of a HybridWebView using the process here.

    It worked fine but broke with 2.5. I've got the code as follows but it at compile time it says "Android.Webkit does not exist" even though I have the using for WebKit and it doesn't complain. Pretty perplexed.

    Forms.Context being obsolete shouldn't have any effect on whether you can compile your example. I'm guessing something else is amiss.

      public class HybridWebViewRenderer2 : ViewRenderer<HybridWebView2, Android.Webkit.WebView>
      {
          const string JavaScriptFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
          Context _context;
    
          public HybridWebViewRenderer2(Context context) : base(context)
          {
                  _context = context;
          }
    

    ….

    Should I have that Context property? And/or do I need to set it/update somehow using CrossCurrent Activity referenced above instead?

    AFAICT, that Context property isn't used for anything. You probably don't need it.

  • EZHartEZHart Xamurai USXamarin Team Xamurai

    @Pierre-ChristopheDus said:
    There is something that I don't understand.

    Before this, I use a CustomRenderer like this:

    [assembly: ExportRenderer(typeof(CircleIconButton), typeof(CircleIconButtonRenderer))]
    namespace MyProject.Droid.Renderers
    {
    
        public class CircleIconButtonRenderer : ButtonRenderer
        {
            protected override void OnDraw(Android.Graphics.Canvas canvas)
            {
                base.OnDraw(canvas);
            }
          ...
      }
    }
    

    All worked fine: I was entering in the OnDraw() method.

    However with XF 2.5, I must do this:

    [assembly: ExportRenderer(typeof(CircleIconButton), typeof(CircleIconButtonRenderer))]
    namespace MyProject.Droid.Renderers
    {
    
        public class CircleIconButtonRenderer : ButtonRenderer
        {
    
            public CircleIconButtonRenderer(Context context) : base(context)
            {
    
            }
    
            protected override void OnDraw(Android.Graphics.Canvas canvas)
            {
                base.OnDraw(canvas);
            }
          ...
      }
    }
    

    This time it's not working: I don't enter in the OnDraw() method.

    Is this normal?

    No, as far as I can tell your OnDraw method should still be called. Is this the only thing which has changed?

  • dpedrinhadpedrinha ✭✭✭ DEMember ✭✭✭
    edited February 2018

    YOU ARE UNBELIEVABLE!!! You remove something that was working because it was ICKY and don't give a proper solution??? Now I have to use somebody else's plugin? What if I tell you that IT IS NOT WORKING?? What if I tell you that for it to work, even if I don't have another Application, I have to do things that aren't explained in the git or readme.txt file from the plugin??
    The worst thing is that it happens EVERY TIME I have to update something from you... Just UNBELIEVABLE!!!
    You should start over.... honestly. It's becoming a frankenstein.

  • EZHartEZHart Xamurai USXamarin Team Xamurai

    @dpedrinha said:
    YOU ARE UNBELIEVABLE!!! You remove something that was working because it was ICKY and don't give a proper solution??? Now I have to use somebody else's plugin? What if I tell you that IT IS NOT WORKING??

    Several alternatives which do not require a plugin have been given upthread. You can use Android.App.Application.Context, you can keep a static reference to your main Activity (which is what Forms.Context does), you can use the local Context where available ...

    You can even just keep using Forms.Context if you really want, because it has not been removed. It's merely been marked obsolete. It's still there. It still does exactly what it always did.

    What if I tell you that for it to work, even if I don't have another Application, I have to do things that aren't explained in the git or readme.txt file from the plugin??

    If you have run into a situation where Forms.Context used to work and the alternatives posted here do not, please post your situation so we can help you address it.

  • CarLoOSXCarLoOSX ✭✭ USMember ✭✭

    @BrandonMinnick said:
    There are two questions here:
    1. How do I update my Custom Renderers to use a local context?
    2. How can I access the current context now that Xamarin.Forms.Forms.Context is obsolete?

    How to Update Custom Renderers

    Add the overloaded Constructor to each custom renderer

    Here is an example using a ButtonRenderer

    [assembly: ExportRenderer(typeof(CustomButton), typeof(CustomButtonRenderer))]
    namespace MyApp.Droid
    {
      public class CustomButtonRenderer : ButtonRenderer
      {
            public CustomButtonRenderer(Context context) : base(context)
            {
                
            }
    
          protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
          {
              base.OnElementPropertyChanged(sender, e);
              //ToDo: Customize Button
          }
      }
    }
    

    How to Access The Current Context

    Install @Motz's CurrentActivityPlugin.

    Now, when you can call CrossCurrentActivity.Current.Activity when you need to access the current activity.

    Here's an example of how to open the App's Settings in Xamarin.Forms.

    [assembly: Dependency(typeof(DeepLinks_Android))]
    namespace MyApp.Droid
    {
        public class DeepLinks_Android : IDeepLinks
        {
            Context CurrentContext => CrossCurrentActivity.Current.Activity;
    
            public Task OpenSettings()
            {
                var myAppSettingsIntent = new Intent(Settings.ActionApplicationDetailsSettings, Android.Net.Uri.Parse("package:" + CurrentContext.PackageName));
                myAppSettingsIntent.AddCategory(Intent.CategoryDefault);
    
                return Task.Run(() =>
                {
                    try
                    {
                        CurrentContext.StartActivity(myAppSettingsIntent);
                    }
                    catch (Exception)
                    {
                        Toast.MakeText(CurrentContext.ApplicationContext, "Unable to open Settings", ToastLength.Short);
                    }
                });
            }
        }
    }
    

    CrossCurrentActivity.Current.Activity; --> Not available with NetStandard ... :(

  • BrandonMinnickBrandonMinnick Xamurai USXamarin Team Xamurai
    edited March 2018
    @CarLoOSX You don’t use the Current Activty Plugin within a NetStandard project. It’s only needed for the Xamarin.Android project.
  • AgileNickAgileNick ✭✭ Member ✭✭

    Agree with many on here - this change is dumb. It worked, leave it alone. Why make people go though this much pain and change code constantly.
    Are you really saying the only way to make Xamarin Forms work is with a plug-in? REALLY - then it's an incomplete framework. I just want to send a notification from a dependency service - but I need a Context to set up the intent and pendingIntent.

    I'm new to Xamarin and that may be apparent - but we just want a simple way of doing things

  • NSouthManNSouthMan ✭✭ USMember ✭✭

    @Motz 's CurrentActivityPlugin is not working for me. I'm calling Init() in my MainActivity, but CrossCurrentActivity.Current.Activity is always NULL.

    protected override void OnCreate(Bundle bundle)
            {
                TabLayoutResource = Resource.Layout.Tabbar;
                ToolbarResource = Resource.Layout.Toolbar;
    
                base.OnCreate(bundle);
    
                Microsoft.WindowsAzure.MobileServices.CurrentPlatform.Init();                        
                global::Xamarin.Forms.Forms.Init(this, bundle);    
    
                CrossCurrentActivity.Current.Init(this, bundle);
                // CrossCurrentActivity.Current.Activity is NULL
                CrossCurrentActivity.Current.Activity = this;
                // CrossCurrentActivity.Current.Activity is still NULL
    
                var formsApp = new App(new AndroidInitializer());
                _registryContainer = formsApp.Container;    
                LoadApplication(formsApp);
            }
    
Sign In or Register to comment.