Event inside a custom renderer not firing - android

I'm making a xamarin.form app,
I have a custom render for streaming the camera inside a view , I've followed the tutorial here:
camera stream tutorial
I've managed to correctly show the camera stream inside a view, now I'm trying to create a button that toggle the flash:
here is my custom renderer:
using System;
using System.Diagnostics;
using Hangover.Camera.Factory;
using Hangover.GUIPersonalizzata;
using Xamarin.Forms;
//Questa classe permette di visualizzare lo stream della fotocamera
namespace Hangover.Camera.Views
{
public class CameraStream:View
{
public static readonly BindableProperty CameraProperty = BindableProperty.Create(
propertyName: "Camera",
returnType: typeof(CameraOptions),
declaringType: typeof(CameraStream),
defaultValue: CameraOptions.Rear);
//Funzioni Fotocamera
private Fotocamera _fotocamera;
public event EventHandler flash_event;
//costruttore di convenienza
public CameraStream()
{
}
public CameraStream(Fotocamera fotocamera){
_fotocamera = fotocamera;
}
public CameraOptions Camera
{
get { return (CameraOptions)GetValue(CameraProperty); }
set { SetValue(CameraProperty, value); }
}
public void gestisciFlash(BottoneSwitch btn){
if (flash_event != null)
flash_event.Invoke(btn, null);
}
}
}
and here is the iOS implementation:
using System;
using System.Diagnostics;
using Hangover.Camera.Factory;
using Hangover.Camera.Views;
using Hangover.GUIPersonalizzata;
using Hangover.iOS.CustomRenderer;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(CameraStream), typeof(CameraStreamRenderer))]
namespace Hangover.iOS.CustomRenderer
{
public class CameraStreamRenderer : ViewRenderer<CameraStream, UICameraPreview>,ICameraFunctions
{
UICameraPreview uiCameraPreview; // fotocamera nativa
protected override void OnElementChanged(ElementChangedEventArgs<CameraStream> elem)
{
base.OnElementChanged(elem);
if (Control == null)
{
uiCameraPreview = new UICameraPreview(elem.NewElement.Camera);
SetNativeControl(uiCameraPreview);
var camera_stream = (CameraStream)elem.NewElement;
camera_stream.flash_event += (object sender, EventArgs e) => {
Debug.Write("teeeest");
};
}
}
//Invocato quando la view viene chiusa
protected override void Dispose(bool disposing)
{
if (disposing)
{
Control.CaptureSession.Dispose();
Control.Dispose();
}
base.Dispose(disposing);
}
public void attivaFlash()
{
Debug.Write("flash");
Debug.Write("flash");
Debug.Write("flash");
}
public void distattivaFlash()
{
throw new NotImplementedException();
}
public void scattaFoto()
{
throw new NotImplementedException();
}
public void ruotaFotocamera(BottoneSwitch btn)
{
if (!btn.isON){// mostro la post
Debug.Write("mostra fotocamera post");
}else{//mostra ant
Debug.Write("mostra fotocamera ant");
}
}
}
}
so using this event:
public void gestisciFlash(BottoneSwitch btn){
if (flash_event != null)
flash_event.Invoke(btn, null);
}
I should be able to print "teeest" but flash_event is always null and I don't know what to do.
I've tried the solution presented here firing custom render event but with no luck.
Any suggestions?
EDIT
protected override void OnElementChanged(ElementChangedEventArgs<CameraStream> e)
{
if(e.OldElement != null){
}
if(e.NewElement != null){
if (Control == null)
{
uiCameraPreview = new UICameraPreview(new Camera.CameraOptions());
SetNativeControl(uiCameraPreview);
}
var camera_stream = (CameraStream)e.NewElement;
camera_stream.flash_event += (object sender, EventArgs ea) => {
attivaFlash();
};
}
base.OnElementChanged(e);
}
refactored the code as suggested by #Benl still not working , this:
public void gestisciFlash(BottoneSwitch btn){
if (flash_event != null)
flash_event.Invoke(btn, null);
}
Is still null.

Edit:
Assumption: Some camera device generating the flash/torch light event is attached.
CameraStreamRenderer.cs:
OnElementChanged(ElementChangedEventArgs<CameraStream> e)
{
base.OnElementChanged(e);
if (Control == null)
{
// Instantiate the native control and assign it to the Control property with
// the SetNativeControl method
uiCameraPreview = new UICameraPreview(e.NewElement.Camera);
SetNativeControl(uiCameraPreview);
}
if (e.OldElement != null)
{
// Unsubscribe from event handlers and cleanup any resources
uiCameraPreview.Flash -= OnFlash;
}
if (e.NewElement != null)
{
// Configure the control and subscribe to event handlers
uiCameraPreview.Flash += OnFlash;
}
}
void OnFlash(object sender, EventArgs e)
{
Debug.WriteLine("Flash");
}
However, the flash event must be generated somewhere, not shown in the original question.
Assuming the flash event might be generated in UICameraPreview in an Observer with assumed key "torchActive".
UICameraPreview.cs:
public event EventHandler<EventArgs> Flash;
protected virtual void OnFlash()
{
Flash?.Invoke(this, new EventArgs());
}
void Initialize()
{
...
AVCaptureDevice device = ...
...
if (device == null)
{
return;
}
device.AddObserver(
key: "torchActive",
options: NSKeyValueObservingOptions.New,
observer: (c) =>
{
OnFlash();
});
device.RemoveObserver not shown above.

Related

Cannot modify managed objects outside of a write transaction

I must push a object just with one note and to push his media files, but it do not want to do this. I do not know why is difference , but if i do this without changing notes its work fine , but I need to send just changed notes , please help. What is my problem? I need to understand why this happen
private void pushMediaFiles(Task task,Note newNote, PushTaskListener listener)
{
PushTaskModel pushTaskModel=new PushTaskModel(Realm.getDefaultInstance().copyFromRealm(task));
pushTaskModel.notes.clear();
Note oldNote = new Note();
oldNote.update(newNote);
pushTaskModel.notes.add(oldNote);
service.pushData(pushTaskModel)
.map(taskResponse ->
{
ArrayList<MediaFile> filesToSave = new ArrayList<>();
for (Note note : task.getNotes())
{
for (MediaFile mediaFile : note.getMediaFiles())
{
if (mediaFile.hasChanges() && mediaFile.getFileAbsolutePath() != null)
{
filesToSave.add(Realm.getDefaultInstance().copyFromRealm(mediaFile));
}
}
}
return filesToSave;
})
.subscribe(taskMediaFiles ->
{
Log.d("PUSH_TASK_EVENT","3 GOOD");
if (taskMediaFiles.size() == 0)
{
listener.onSuccess();
return;
}
for (MediaFile mediaFile: taskMediaFiles)
{
saveMediaFile(mediaFile, new OnResponseListener()
{
#Override
public void onStart()
{
}
#Override
public void onComplete(boolean successfully)
{
listener.onSuccess();
}
#Override
public void onError(Throwable throwable)
{
listener.onError(throwable);
}
});
}
});
}

Custom renderer for Tabbed page in Android: Xamarin Forms

I am working on a Xamarin.Forms application. I have this tabbed page renderer in iOS:
public class TabbedPageRenderer : TabbedRenderer
{
private MainPage _page;
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
_page = (MainPage)e.NewElement;
}
else
{
_page = (MainPage)e.OldElement;
}
try
{
var tabbarController = (UITabBarController)this.ViewController;
if (null != tabbarController)
{
tabbarController.ViewControllerSelected += OnTabBarReselected;
}
}
catch (Exception exception)
{
Console.WriteLine(exception);
}
}
private void OnTabBarReselected(object sender, UITabBarSelectionEventArgs e)
{
var tabs = Element as TabbedPage;
var playTab = tabs.Children[4];
if (TabBar.SelectedItem.Title == "Play")
{
if (tabs != null)
{
playTab.Title = "Pause";
playTab.Icon = "pause.png";
}
App.pauseCard = false;
}
else
{
if (tabs != null)
{
playTab.Title = "Play";
playTab.Icon = "play.png";
}
App.pauseCard = true;
}
}
}
This basically changes the icon on my Play tab to pause and play. This is working good in iOS. But I am struggling on how to have the same function (basically convert this to Android) in Android side.
Can any point me to the right direction? Basically help me? :-)
Note: I am pretty new with Android development.
EDIT: This is what it would look like in iOS.
Pause Mode:
Play Mode:
There is a blog post by Xamarin's James Montemagno which explains how to achieve this requirement.
Basically, it uses Custom Tab which inherits from TabbedPage which initialize an event UpdateIcons to be fired on Tab Current Page Changed event CurrentPageChanged
public class MyTabs : TabbedPage
{
//always save a reference to the current page
Page currentPage;
public MyTabs()
{
//create the pages and set the view models
//you could also do this in the page code behind
Children.Add(new TabIconsPage
{
BindingContext = new Tab1ViewModel
{
IsSelected = true
}
});
Children.Add(new TabIconsPage2
{
BindingContext = new Tab2ViewModel()
});
currentPage = Children[0];
//Register for page changes
this.CurrentPageChanged += Handle_CurrentPageChanged;
}
//Update the IsSelected state and trigger an Event that anyone can loop into.
public event EventHandler UpdateIcons;
void Handle_CurrentPageChanged(object sender, EventArgs e)
{
var currentBinding = currentPage.BindingContext as IIconChange;
if (currentBinding != null)
currentBinding.IsSelected = false;
currentPage = CurrentPage;
currentBinding = currentPage.BindingContext as IIconChange;
if (currentBinding != null)
currentBinding.IsSelected = true;
UpdateIcons?.Invoke(this, EventArgs.Empty);
}
}
Now Android needs a custom renderer to subscribe to the UpdateIcons event and perform icon changes
public class MyTabs : TabbedPage
{
//always save a reference to the current page
Page currentPage;
public MyTabs()
{
//create the pages and set the view models
//you could also do this in the page code behind
Children.Add(new TabIconsPage
{
BindingContext = new Tab1ViewModel
{
IsSelected = true
}
});
Children.Add(new TabIconsPage2
{
BindingContext = new Tab2ViewModel()
});
currentPage = Children[0];
//Register for page changes
this.CurrentPageChanged += Handle_CurrentPageChanged;
}
//Update the IsSelected state and trigger an Event that anyone can loop into.
public event EventHandler UpdateIcons;
void Handle_CurrentPageChanged(object sender, EventArgs e)
{
var currentBinding = currentPage.BindingContext as IIconChange;
if (currentBinding != null)
currentBinding.IsSelected = false;
currentPage = CurrentPage;
currentBinding = currentPage.BindingContext as IIconChange;
if (currentBinding != null)
currentBinding.IsSelected = true;
UpdateIcons?.Invoke(this, EventArgs.Empty);
}
}

OnElementChanged(...) not getting the new value of property

I have a a map that displays a route between points. This is working but now I want to pass some color values from PCL. I have a custom renderer for android and a class acting as bridge between PCL and android with bindable properties in it.
(For the map I use the PCL implementation and extends it with the custom renderer)
For now I got:
(android : CustomMapRenderer)
[assembly: ExportRenderer(typeof(CustomMap), typeof(MapOverlay.Droid.CustomMapRenderer))]
namespace MapOverlay.Droid
{
public class CustomMapRenderer : MapRenderer , ICustomMap
{
List<Position> routeCoordinates;
Int32 RouteColor;
protected override void OnElementChanged(Xamarin.Forms.Platform.Android.ElementChangedEventArgs<Map> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
// Unsubscribe
}
if (e.NewElement != null)
{
var formsMap = (CustomMap)e.NewElement;
routeCoordinates = formsMap.RouteCoordinates;
Control.GetMapAsync(this);
}
}
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (this.Element == null || this.Control == null)
return;
if (e.PropertyName == "VisibleRegion")
{
var polylineOptions = new PolylineOptions();
polylineOptions.InvokeColor(0x66FF0000);
foreach (var position in routeCoordinates)
{
polylineOptions.Add(new LatLng(position.Latitude, position.Longitude));
}
NativeMap.AddPolyline(polylineOptions);
}
}
public void working()
{
Log.Debug("xxxx", "WORKING");
}
}
}
Now the class acting as bridge from PCL to Android
(PCL : CustomMap)
namespace SomeNamespace
{
public class CustomMap : Map
{
public static readonly BindableProperty RouteCoordinatesProperty =
BindableProperty.Create<CustomMap, List<Position>>(p => p.RouteCoordinates, new List<Position>());
public static readonly BindableProperty RouteColorProperty =
BindableProperty.Create<CustomMap, int>(p => p.RouteColor, 0x66FF0000);
//Property used to add points to the map. Then polyline utility will draw a line beteween thoses points
public List<Position> RouteCoordinates
{
get { return (List<Position>)base.GetValue(RouteCoordinatesProperty); }
set {
base.SetValue(RouteCoordinatesProperty, value);
}
}
public Int32 RouteColor
{
get { return (Int32)base.GetValue(RouteColorProperty); }
set { base.SetValue(RouteColorProperty, value); }
}
public CustomMap()
{
RouteCoordinates = new List<Position>();
}
}
}
So as you can see I added an Int32 RouteColor variable wich should be used in the CustomMapRenderer to change the color of the displayed route.
But when I change this value in my PCL code like so :
mCustomMap.RouteColor = 0x22AA0000;
It triggers an OnElementPropertyChanged response but not an OnElementChanged event.
So I can't get the changed value. I only know that it DID changed (with the OnElementPropertyChanged).
If someone knows how to bypass this phenomena... All suggestions are welcome ;-)
Thanks in advance !
Element represents the forms element, which in this case should be CustomMap. You can use it to retrieve the property values.
For e.g.:
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (this.Element == null || this.Control == null)
return;
....
//if RouteColor is the property that changed
if (e.PropertyName == nameof(CustomMap.RouteColor))
{
//get new value
var newColor = (Element as CustomMap)?.RouteColor;
//and, update native control
....

Chromecast MediaRouteProviderService

I have strange issue, I am creating mediaprovider for chromecast using following code that works fine for first instance, list of devices is shown and once slected I use router.selectRoute(routeinfo);
but once I exit app this code unable to find Chromecast device, how ever when I remove app from running apps stack this code works fine again and show devices.
If no device is selected and app is exited using back press then also this code works fine
So what I am doing wrong here? what I think is resources are not cleared when my app exit in simple back pressed.
public class ChromecastRouteProviderService extends MediaRouteProviderService {
final String LOGTAG = "Chromecast";
private static final String CONTROL_CATEGORY = CastMediaControlIntent.categoryForCast(CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID);
private static final MediaRouteSelector SELECTOR = new MediaRouteSelector.Builder().addControlCategory(CONTROL_CATEGORY)
.addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK).build();
private IntentFilter controlFilter;
public ChromecastRouteProviderService() {
controlFilter = new IntentFilter();
}
public void onCreate() {
super.onCreate();
controlFilter.addCategory(IAppConstants.CATEGORY);
controlFilter.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
}
#Override
public MediaRouteProvider onCreateMediaRouteProvider() {
return new ChromecastRouteProvider(this);
}
class ChromecastRouteProvider extends MediaRouteProvider {
MediaRouter.Callback callback;
Hashtable routes;
public ChromecastRouteProvider(Context context) {
super(context);
routes = new Hashtable();
callback = new CastCallBack();
}
#Nullable
#Override
public RouteController onCreateRouteController(String routeId) {
MediaRouter.RouteInfo routeInfo = (MediaRouter.RouteInfo) routes.get(routeId);
if (routeInfo == null) {
return super.onCreateRouteController(routeId);
} else {
return new ChromecastRouteController(getContext(), routeInfo);
}
}
#Override
public void onDiscoveryRequestChanged(#Nullable MediaRouteDiscoveryRequest request) {
super.onDiscoveryRequestChanged(request);
if (request == null || !request.isActiveScan() || !request.isValid()) {
stopScan();
return;
}
if (!request.getSelector().hasControlCategory(IAppConstants.CATEGORY)) {
Log.i(LOGTAG, "Not scanning for non remote playback");
stopScan();
return;
} else {
Log.i(LOGTAG, "Scanning...");
mediarouter.addCallback(ChromecastRouteProviderService.SELECTOR, callback, MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
return;
}
}
void updateDescriptor() {
final MediaRouteProviderDescriptor.Builder descriptor = new MediaRouteProviderDescriptor.Builder();
for (Iterator iterator = routes.values().iterator(); iterator.hasNext(); ) {
MediaRouter.RouteInfo routeinfo = (MediaRouter.RouteInfo) iterator.next();
try {
Bundle bundle = new Bundle();
bundle.putBoolean("has_upsell", true);
descriptor.addRoute(new MediaRouteDescriptor.Builder(routeinfo.getId(), routeinfo.getName())
.addControlFilter(controlFilter).setPlaybackStream(3)
.setDescription(routeinfo.getDescription())
.setEnabled(true).setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
.setVolumeHandling(1).setVolumeMax(100).setVolume(100)
.setExtras(bundle).build());
} catch (Exception e) {
throw new Error("wtf");
}
}
getHandler().post(new Runnable() {
#Override
public void run() {
setDescriptor(descriptor.build());
}
});
}
void stopScan() {
Log.i(LOGTAG, "Stopping scan...");
try {
MediaRouter.getInstance(getContext()).removeCallback(callback);
return;
} catch (Exception exception) {
return;
}
}
class CastCallBack extends MediaRouter.Callback {
void check(MediaRouter mediarouter, MediaRouter.RouteInfo routeinfo) {
Log.i(LOGTAG, new StringBuilder().append("Checking route ").append
(routeinfo.getName()).toString());
CastDevice device = CastDevice.getFromBundle(routeinfo.getExtras());
if (routeinfo.matchesSelector(ChromecastRouteProviderService.SELECTOR)
&& device != null && device.isOnLocalNetwork()) {
routes.put(routeinfo.getId(), routeinfo);
updateDescriptor();
return;
} else {
return;
}
}
public void onRouteAdded(MediaRouter mediarouter, MediaRouter.RouteInfo routeinfo) {
super.onRouteAdded(mediarouter, routeinfo);
check(mediarouter, routeinfo);
}
public void onRouteChanged(MediaRouter mediarouter, MediaRouter.RouteInfo routeinfo) {
super.onRouteChanged(mediarouter, routeinfo);
check(mediarouter, routeinfo);
}
public void onRouteRemoved(MediaRouter mediarouter, MediaRouter.RouteInfo routeinfo) {
super.onRouteRemoved(mediarouter, routeinfo);
if (routeinfo.matchesSelector(ChromecastRouteProviderService.SELECTOR)) ;
}
}
}
}
Ok finally I found answer on my own,
Problem is when any provider is selected it's not added using onRouteAdded why? I really dont understand google logic
So the solution is to unselect the router when you want or better select default route when so that your route is released
MediaRouter.getInstance(this).getDefaultRoute().select();
But again 1 out of 10 times it will not work
Hope will help someone

Show 3dmodel after tracking lost metaio sdk

I am using metaio sdk 6.0.2. i am working on metaio INSTANT_2D_GRAVITY tracking and was able to display 3d model. I want to display same 3d model when tracking is lost.but I am failing to do so. I tried by adding trackingValuesVector in onTrackingEvent of MetaioSDKCallbackHandler with no success. can anyone tell me where am I going wrong?
private TrackingValues mTrackingValues;// declared globally
private IGeometry mModel; // declared globally
private boolean mPreview=true;// declared globally
// start INSTANT_2D_GRAVITY tracking
public void onTakePicture(View v)
{
captureTrackingValues = true;
metaioSDK.startInstantTracking("INSTANT_2D_GRAVITY", new File(""), mPreview);
mPreview = !mPreview;
}
final class MetaioSDKCallbackHandler extends IMetaioSDKCallback
{
#Override
public void onInstantTrackingEvent(final boolean success,final File filePath) {
super.onInstantTrackingEvent(success, filePath);
if(mSurfaceView != null)
{
mSurfaceView.queueEvent(new Runnable() {
#Override
public void run() {
if(success)
{
if(captureTrackingValues == true)
{
metaioSDK.setTrackingConfiguration(filePath);
Log.i("Tracking value success","good");
}
}
else
{
Log.i("Tracking value failure","bad");
}
}
});
}
}
#Override
public void onTrackingEvent(TrackingValuesVector trackingValuesVector) {
super.onTrackingEvent(trackingValuesVector);
if (!trackingValuesVector.isEmpty())
{
for(int i =0;i< trackingValuesVector.size();i++)
{
if(trackingValuesVector.get(i).isTrackingState() && mModel!=null)
{
mTrackingValues = metaioSDK.getTrackingValues(i);
mModel.setCoordinateSystemID(trackingValuesVector.get(i).getCoordinateSystemID());
}
else {
if(mModel!= null && mTrackingValues != null) {
metaioSDK.setCosOffset(1, mTrackingValues);
//mChairModel.setCoordinateSystemID(0);
Log.e("TestAR","isTrackingState is null");
}
}
}
}
else{
if(mModel!= null && mTrackingValues != null) {
metaioSDK.setCosOffset(1, mTrackingValues);
//mModel.setCoordinateSystemID(0);
Log.e("TestAR","trackingValuesVector is null");
}
}
}
}
loading 3d model:
private void loadModel()
{
if (mSurfaceView != null) {
mSurfaceView.queueEvent(new Runnable() {
#Override
public void run() {
File chairModel = AssetsManager.getAssetPathAsFile(getApplicationContext(),"chair.obj");
if (chairModel != null) {
mModel = metaioSDK.createGeometry(chairModel);
mModel.setScale(3f);
mModel.setTranslation(new Vector3d(0f,0f,-60f));
mGestureHandler.addObject(mModel, 1);
mModel.setRotation(new Rotation(0f, 0.5f, 0f));
mModel.setCoordinateSystemID(1);
}
}
});
}
else
{
Log.e("exception", "msurfaceview is null");
}
}
I see that you also tried setting the model to COS 0. This should actually work, if the tracking is lost.
If you do not see the model, you would have to play around with the scale value (i.e. set a low value like 0.01) and with the Z translation value. Set a negative Z value in order to move the model away from the camera clipping plane.

Categories

Resources