Xamarin Forms - iOS Custom Renderer - Disposing of observers - xamarin.ios

The following crashes when the page contening the ListView being customized disapears. The errors says my observers are not disposed of.
public class ChatListViewRenderer : ListViewRenderer
{
private IDisposable _onContentSizeChangedObserver;
private IDisposable _onFrameChangedObserver;
protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
{
base.OnElementChanged(e);
if (Control == null) return;
_onContentSizeChangedObserver = Control.AddObserver(new NSString("contentSize"), NSKeyValueObservingOptions.OldNew, OnContentSizeChanged);
_onFrameChangedObserver = Control.AddObserver(new NSString("frame"), NSKeyValueObservingOptions.OldNew, OnFrameChanged);
}
protected override void Dispose(bool disposing)
{
_onContentSizeChangedObserver.Dispose();
_onFrameChangedObserver.Dispose();
base.Dispose(disposing);
}
}
UPDATE
I tried within ViewWillDisapear of the PageRenderer of the page containing the ListView but the issue remains.
public override void ViewWillDisappear(bool animated)
{
base.ViewWillDisappear(animated);
var listRenderer = Platform.CreateRenderer(((MessagesPage)Element).MessagesList) as ChatListViewRenderer;
listRenderer._onContentSizeChangedObserver.Dispose();
listRenderer._onFrameChangedObserver.Dispose();
}

OnElementChanged is called twice - when control is created and then when disposed.
if(e.NewElement != null)
addobserver and do other stuff
if(e.oldElement!=null)
remove observer and destroy everything you allocated

A more "visual" example:
private IDisposable _onAppOrientationChangedObserver;
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged (e);
if (e.OldElement != null)
{
_onAppOrientationChangedObserver?.Dispose();
}
if (e.NewElement != null)
{
if (Control!=null)
{
_onAppOrientationChangedObserver = NSNotificationCenter.DefaultCenter.AddObserver(UIDevice.OrientationDidChangeNotification, (obj) =>
{
_uiOrientation = UIApplication.SharedApplication.StatusBarOrientation;
_deviceOrientation = UIDevice.CurrentDevice.Orientation;
});
}
}
}

Related

Getting Error while Overriding GenerateProductionOrders Method of MRPDisplay graph in acumatica

Getting Error while Overriding GenerateProductionOrders Method of MRPDisplay graph in acumatica.
I have Extend MRPdisplay graph.
protected delegate void GenerateProductionOrdersDelegate(List<AMOrderCrossRef> list);
[PXOverride]
protected virtual void GenerateProductionOrders(List<AMOrderCrossRef> list, GenerateProductionOrdersDelegate baseMethod) {
if(list == null || list.Count == 0) {
return;
}
if(list.Count == 1) {
PXLongOperation.StartOperation(this, () => {
CreateProductionOrders(PXGraph.CreateInstance<ProdMaint>(), list);
});
return;
}
}
please try this one:
public class MRPDisplay_Extension : PXGraphExtension<MRPDisplay>
{
[PXOverride]
public void GenerateProductionOrders(List<AMOrderCrossRef> list, Action<List<AMOrderCrossRef>> baseMethod)
{
baseMethod(list);
}
}
You can always go to the Code editor and check if you can override or not the method:

How to detect recent button in android 8.1.0

I want to detect recent button but in android 8.1.0 it's not working.Below code is working on another version of android but in 8.1.0 the Intent.ACTION_CLOSE_SYSTEM_DIALOGS broadcast is not calling.I am using below implementation.
public class HomeWatcher {
static final String TAG = "hg";
private Context mContext;
private IntentFilter mFilter;
private OnHomePressedListener mListener;
private InnerRecevier mRecevier;
public HomeWatcher(Context context) {
mContext = context;
mFilter = new IntentFilter();
mFilter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
mFilter.addAction("");
}
public void setOnHomePressedListener(OnHomePressedListener listener) {
mListener = listener;
mRecevier = new InnerRecevier();
}
public void startWatch() {
if (mRecevier != null) {
mContext.registerReceiver(mRecevier, mFilter);
}
}
public void stopWatch() {
if (mRecevier != null) {
mContext.unregisterReceiver(mRecevier);
}
}
class InnerRecevier extends BroadcastReceiver {
final String SYSTEM_DIALOG_REASON_KEY = "reason";
final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions";
final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) {
String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY);
if (reason != null) {
Log.e(TAG, "action:" + action + ",reason:" + reason);
if (mListener != null) {
if (reason.equals(SYSTEM_DIALOG_REASON_HOME_KEY)) {
mListener.onHomePressed();
} else if (reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) {
mListener.onHomeLongPressed();
}
}
}
}
}
}
}
and in class, I am calling using below code.
HomeWatcher mHomeWatcher = new HomeWatcher(this);
mHomeWatcher.startWatch();
Please help!.
Edited----
The above code is working properly in normal flow but when the screen pinning is set(ON) then it's not working. Even i am not getting any event like KeyUp, KeyDown
com.android.systemui package will be the foreground app when you click recent app button, so find the foreground running apps and launch your page if foreground running app is 'com.android.systemui'.
HomeWatcher mHomeWatcher = new HomeWatcher(this);
mHomeWatcher.setOnHomePressedListener(new OnHomePressedListener() {
#Override
public void onHomePressed() {
// do something here...
}
#Override
public void onHomeLongPressed() {
}
});
mHomeWatcher.startWatch();
For a detailed Answer Check
Detect Home And Recent App Button
Please use below code
`#Override
public boolean dispatchKeyEvent(KeyEvent event) {
Log.i("key pressed", String.valueOf(event.getKeyCode()));
return super.dispatchKeyEvent(event);
}`

Xamarin QLPreviewController + NavigationPage broken on iOS 10

After updating the device to iOS 10, QLPreviewController stopped to display correctly the documents. It shows the white screen.
I have extracted the sample scenario from the app.
It contains single page with two buttons that should load two different documents:
<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:QuickLookIOS10Test"
x:Class="QuickLookIOS10Test.QuickLookIOS10TestPage">
<StackLayout Orientation="Vertical">
<Button Text="Load first doc" Clicked="OnLoadFirstClicked"/>
<Button Text="Load second doc" Clicked="OnLoadSecondClicked"/>
<Button Text="Navigate forward" Clicked="OnForwardClicked"/>
<local:QLDocumentView
x:Name="DocumentView"
BackgroundColor="Silver"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"/>
</StackLayout>
</ContentPage>
where:
public class QLDocumentView : View
{
public static readonly BindableProperty FilePathProperty =
BindableProperty.Create(nameof(FilePath), typeof(string), typeof(QLDocumentView), null);
public string FilePath
{
get { return (string)GetValue(FilePathProperty); }
set { SetValue(FilePathProperty, value); }
}
}
There is a custom renderer involved:
public class QLDocumentViewRenderer : ViewRenderer<QLDocumentView, UIView>
{
private QLPreviewController controller;
public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
{
//This is a fix to prevent incorrect scaling after rotating from portrait to landscape.
//No idea why does this work :( Bug #101639
return new SizeRequest(Size.Zero, Size.Zero);
}
protected override void OnElementChanged(ElementChangedEventArgs<QLDocumentView> e)
{
base.OnElementChanged(e);
if (Control == null)
{
controller = new QLPreviewController();
SetNativeControl(controller.View);
}
RefreshView();
}
protected override void OnElementPropertyChanged(object sender,
System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == QLDocumentView.FilePathProperty.PropertyName)
{
RefreshView();
}
}
private void RefreshView()
{
DisposeDataSource();
if (Element?.FilePath != null)
{
controller.DataSource = new DocumentQLPreviewControllerDataSource(Element.FilePath);
}
controller.ReloadData();
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
DisposeDataSource();
DisposeController();
}
}
private void DisposeDataSource()
{
var dataSource = controller.DataSource;
controller.DataSource = null;
dataSource?.Dispose();
}
private void DisposeController()
{
controller?.Dispose();
controller = null;
}
private class DocumentQLPreviewControllerDataSource : QLPreviewControllerDataSource
{
private readonly string fileName;
public DocumentQLPreviewControllerDataSource(string fileName)
{
this.fileName = fileName;
}
public override nint PreviewItemCount(QLPreviewController controller)
{
return 1;
}
public override IQLPreviewItem GetPreviewItem(QLPreviewController controller, nint index)
{
NSUrl url = NSUrl.FromFilename(fileName);
return new QlItem(url);
}
private sealed class QlItem : QLPreviewItem
{
private readonly NSUrl itemUrl;
public QlItem(NSUrl uri)
{
itemUrl = uri;
}
public override string ItemTitle { get { return string.Empty; } }
public override NSUrl ItemUrl { get { return itemUrl; } }
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
this.itemUrl?.Dispose();
}
}
}
}
}
If the application setups the main page like below:
MainPage = new NavigationPage(new QuickLookIOS10TestPage());
it does work on iOS 9.3 but not on iOS 10. If I remove NavigationPage:
MainPage = new QuickLookIOS10TestPage();
it works on both iOS versions.
The code behind for button clicks just sets the FilePath property of the control.
Sample app demonstrating the problem
Xamarin Forms 2.3.2.127
Xamarin Studio 6.1.1 (build 15)
I've faced with the same problem. It looks like something was changed or even broken in QuickLook in iOS10, but the solution is quite simple:
public class PdfViewerControlRenderer : ViewRenderer<PdfViewerControl, UIView>
{
private readonly bool IsOniOS10;
private UIViewController _controller;
private QLPreviewController _qlPreviewController;
public PdfViewerControlRenderer()
{
IsOniOS10 = UIDevice.CurrentDevice.CheckSystemVersion(10, 0);
}
protected override void OnElementChanged(ElementChangedEventArgs<PdfViewerControl> e)
{
if (e.NewElement != null)
{
_controller = new UIViewController();
_qlPreviewController = new QLPreviewController();
//...
// Set QuickLook datasource here
//...
if (!IsOniOS10)
{
_controller.AddChildViewController(_qlPreviewController);
_controller.View.AddSubview(_qlPreviewController.View);
_qlPreviewController.DidMoveToParentViewController(_controller);
}
SetNativeControl(_controller.View);
}
}
public override void LayoutSubviews()
{
base.LayoutSubviews();
_controller.View.Frame = Bounds;
_controller.View.AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight;
_qlPreviewController.View.Frame = Bounds;
if (IsOniOS10)
{
_controller.View.AddSubview(_qlPreviewController.View);
_qlPreviewController.DidMoveToParentViewController(_controller);
}
}
}
Result:

XNA background music change not work properly

In XNA, i want to manage background sound for every game state. For example; StartMenu music will be playing when option is Start or Playing music will be playing when option is Playing. I arranged code as followed, as you can see i create play and stop method inside all sound class but it's only working while exiting to game.
public abstract class Sound
{
public SoundEffect Item { get; set; }
public Song BgSound { get; set; }
public abstract void LoadContent(ContentManager content);
public abstract void Play();
public abstract void Stop();
}
public class GameSound : Sound
{
public GameSound()
{
BgSound = null;
Item = null;
}
public override void LoadContent(ContentManager content)
{
BgSound = content.Load<Song>("Sounds/BgSound");
MediaPlayer.IsRepeating = true;
}
public override void Play()
{
MediaPlayer.Play(BgSound);
}
public override void Stop()
{
MediaPlayer.Stop();
}
}
public class StartUpSound : Sound
{
public StartUpSound()
{
BgSound = null;
Item = null;
}
public override void LoadContent(ContentManager content)
{
BgSound = content.Load<Song>("Sounds/StartUp");
MediaPlayer.IsRepeating = false;
}
public override void Play()
{
MediaPlayer.Play(BgSound);
}
public override void Stop()
{
MediaPlayer.Stop();
}
}
public class GameBase : Microsoft.Xna.Framework.Game
{
//..
GameSound gameSoundFx = new GameSound();
StartUpSound startUpSoundFx = new StartUpSound();
//..
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
gameSoundFx.LoadContent(Content);
startUpSoundFx.LoadContent(Content);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin();
if (currentState == GameState.StartMenu)
{
startUpSoundFx.Play();
}
if (currentState == GameState.Playing)
{
startUpSoundFx.Stop();
gameSoundFx.Play();
}
spriteBatch.End();
base.Draw(gameTime);
}
}
Edited
Also i tried again like this but nothing has changed;
if (currentState == GameState.StartMenu)
{
startUpSoundFx.BgSound = Content.Load<Song>("Sounds/StartUp");
MediaPlayer.Play(startUpSoundFx.BgSound);
}
if (currentState == GameState.Playing)
{
MediaPlayer.Stop();
gameSoundFx.BgSound = Content.Load<Song>("Sounds/StartUp");
MediaPlayer.Play(gameSoundFx.BgSound);
}
This has already been answered -
https://gamedev.stackexchange.com/questions/86038/c-how-to-properly-play-background-songs-in-xna
Please review the answer there, here's the example included -
switch (currentGameState)
{
case GameState.MainMenu:
if (musicState == MusicState.Playing && currentGameState != lastGameState)
{
MediaPlayer.Stop();
musicState = MusicState.NotPlaying;
}
if (musicState == MusicState.NotPlaying)
{
MediaPlayer.Play(song_mainTheme);
musicState = MusicState.Playing;
}
break;
case GameState.GamePlaying:
if (musicState == MusicState.Playing && currentGameState != lastGameState)
{
MediaPlayer.Stop();
musicState = MusicState.NotPlaying;
}
if (musicState == MusicState.NotPlaying)
{
MediaPlayer.Play(song_actionTheme);
musicState = MusicState.Playing;
}
break;
}
It shows extending the states to allow for proper use of the Mediaplayer - as stated in the comments of your question.

Entity Framework The context cannot be used while the model is being created

My unit of work class is mentioned below and I am using Ninject and I have tried injecting IUnitOfWork per request per thread scope, transient etc. but I am still getting error which is:
"Message":"An error has occurred.","ExceptionMessage":"The context cannot be used while the model is being created. This exception may be thrown if the context is used inside the OnModelCreating method or if the same context instance is accessed by multiple threads concurrently. Note that instance members of DbContext and related classes are not guaranteed to be thread safe.","ExceptionType":"System.InvalidOperationException
I get this error when i make two web API (get) calls at the same time using angularJS and it shows error at the point _context.Set<TEntity>().FirstOrDefault(match);
public class UnitOfWork : IUnitOfWork, IDisposable
{
private My_PromotoolEntities _uowDbContext = new My_PromotoolEntities();
private Dictionary<string, object> _repositories;
// Do it like this if no specific class file
private GenericRepository<MysPerson> _personRepository;
//private GenericRepository<MysDataSource> dataSourcesRepository;
//private GenericRepository<MysCountry> countryMasterRepository;
// Or like this if with specific class file.
private DataSourceRepository _dataSourcesRepository;
private CustomerRepository _customerRepository;
private DeviceRepository _deviceRepository;
private DeviceRegistrationRepository _deviceRegistrationRepository;
private EmailQueueRepository _emailQueueRepository;
public void SetContext(My_PromotoolEntities context)
{
_uowDbContext = context;
}
public void CacheThis(object cacheThis, string keyName, TimeSpan howLong)
{
Cacheing.StaticData.CacheStaticData(cacheThis, keyName, howLong);
}
public object GetFromCache(string keyName)
{
return Cacheing.StaticData.GetFromCache(keyName);
}
public GenericRepository<T> GenericRepository<T>() where T : BaseEntity
{
if (_repositories == null)
{
_repositories = new Dictionary<string, object>();
}
var type = typeof(T).Name;
if (!_repositories.ContainsKey(type))
{
var repositoryType = typeof(GenericRepository<>);
var repositoryInstance = Activator.CreateInstance(repositoryType.MakeGenericType(typeof(T)), _uowDbContext);
_repositories.Add(type, repositoryInstance);
}
return (GenericRepository<T>)_repositories[type];
}
public GenericRepository<MysPerson> PersonRepository
{
get
{
if (this._personRepository == null)
{
this._personRepository = new GenericRepository<MysPerson>(_uowDbContext);
}
return _personRepository;
}
}
public DataSourceRepository DataSourcesRepository
{
get
{
if (this._dataSourcesRepository == null)
{
this._dataSourcesRepository = new DataSourceRepository(_uowDbContext);
}
return _dataSourcesRepository;
}
}
public CustomerRepository CustomerRepository
{
get
{
if (this._customerRepository == null)
{
this._customerRepository = new CustomerRepository(_uowDbContext);
}
return _customerRepository;
}
}
public DeviceRepository DeviceRepository
{
get
{
if (this._deviceRepository == null)
{
this._deviceRepository = new DeviceRepository(_uowDbContext);
}
return _deviceRepository;
}
}
public DeviceRegistrationRepository DeviceRegistrationRepository
{
get
{
if (this._deviceRegistrationRepository == null)
{
this._deviceRegistrationRepository = new DeviceRegistrationRepository(_uowDbContext);
}
return _deviceRegistrationRepository;
}
}
public EmailQueueRepository emailQueueRepository
{
get
{
if (this._emailQueueRepository == null)
{
this._emailQueueRepository = new EmailQueueRepository(_uowDbContext);
}
return _emailQueueRepository;
}
}
/// <summary>
/// Commits all changes to the db. Throws exception if fails. Call should be in a try..catch.
/// </summary>
public void Save()
{
try
{
_uowDbContext.SaveChanges();
}
catch (DbEntityValidationException dbevex)
{
// Entity Framework specific errors:
StringBuilder sb = new StringBuilder();
var eve = GetValidationErrors();
if (eve.Count() > 0)
{
eve.ForEach(error => sb.AppendLine(error));
}
ClearContext();
// Throw a new exception with original as inner.
var ex = new Exception(sb.ToString(), dbevex);
ex.Source = "DbEntityValidationException";
throw ex;
}
catch (Exception)
{
ClearContext();
throw;
}
}
private void ClearContext()
{
DetachAll();
}
private void DetachAll()
{
foreach (DbEntityEntry dbEntityEntry in _uowDbContext.ChangeTracker.Entries())
{
if (dbEntityEntry.Entity != null)
{
dbEntityEntry.State = EntityState.Detached;
}
}
}
/// <summary>
/// Checks for EF DbEntityValidationException(s).
/// </summary>
/// <returns>Returns a List of string containing the EF DbEntityValidationException(s).</returns>
public List<string> GetValidationErrors()
{
if (_uowDbContext.GetValidationErrors().Count() != 0)
{
return _uowDbContext.GetValidationErrors().Select(e => string.Join(Environment.NewLine, e.ValidationErrors.Select(v => string.Format("{0} - {1}", v.PropertyName, v.ErrorMessage)))).ToList();
}
return null;
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
_uowDbContext.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
You should never use a context in 2 places at the same time, that's exactly why you are getting this error. From the MSDN documentation:
Thread Safety: Any public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe.
It is a little hard to make suggestions without a repro but there is a brute force approach that should resolve the issue. If you have an interception point before/during DI setup then you can cause all the context initialization etc to happen by creating an instance of your context and calling ctx.Database.Initialize(force: false); Passing 'force: false' will ensure that the initialization still only happens once per AppDomain

Resources