I am working on Xamarin.iOS application. I want some part of my currently running features should also run in background. I have a System.Timer which is in the App.cs(Load on start) and added TimerElapsed event in one of the viewcontroller through which control passes to ViewModel on timer elapsed and executes all function.
Now I want the same feature should run in background also but unable to get thoughts how to achieve it. Can anyone help me on this?
Thanks.
The simplest way to do it would be something like this
//Some async method
await Task.Run(async () =>
{
// Code you want to run asynchronously
});
Goodluck!
In case of queries revert!
Backround (when you send your application to background with home button) is a little bit complicated in IOS. You can achive in several ways
1) beginBackgroundTaskWithExpirationHandler but it has some limitation with time
2) locationUpdate event - but with timer it will work not very good. it more suteble when you need check something frome time to time
3) Make you app Player. Player can work even if it is in background
Im using method 3, and it works fine for me
You need add permission for background audio in plist file
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
Then in your AppDelegate you need modify FinishedLaunching method
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
...
var currentSession = AVAudioSession.SharedInstance();
currentSession.SetCategory(AVAudioSessionCategory.Playback, AVAudioSessionCategoryOptions.MixWithOthers);
currentSession.SetActive(true);
...
}
And add next section, I also put it in AppDelegat
#region Background Work
private BackgroundTask _backgroundTask;
private readonly Lazy<TouchLogger> _logger = new Lazy<TouchLogger>(() => new TouchLogger());
public override void DidEnterBackground(UIApplication uiApplication)
{
base.DidEnterBackground(uiApplication);
startBackgroundTask();
}
public override void WillEnterForeground(UIApplication uiApplication)
{
base.WillEnterForeground(uiApplication);
if (_backgroundTask == null)
{
_backgroundTask = new BackgroundTask(_logger.Value);
}
else if (_backgroundTask.IsRunned)
{
_backgroundTask.Stop();
_logger.Value.Info("Background", "Task stopped");
}
}
private void startBackgroundTask()
{
_logger.Value.Info("Background", "startBackgroundTask() called");
var needBackgroundActivity = true;// if you timer is running set true, if not false
if (needBackgroundActivity)
{
if (_backgroundTask == null)
_backgroundTask = new BackgroundTask(_logger.Value);
_backgroundTask.Start();
_logger.Value.Info("Background", "Task runned");
}
}
private class BackgroundTask
{
private const string EmptySoundFileName = "Sounds/empty";//wav file, I use 4 sec long file with no sound
private TouchLogger _logger;
private AVAudioPlayer _player;
private NSTimer _debugTimer;
public bool IsRunned { get; private set; }
public BackgroundTask(TouchLogger logger)
{
_logger = logger;
}
public void Start()
{
IsRunned = true;
if (_player != null && _player.Playing)
{
Stop();
}
var bundle = NSBundle.MainBundle.PathForResource(EmptySoundFileName, "wav");
var sound = new NSUrl(bundle);
_player = AVAudioPlayer.FromUrl(sound);
_player.NumberOfLoops = -1;
_player.Volume = 0.1f;
_player.PrepareToPlay();
_player.Play();
startDebugTimer();
}
public void Stop()
{
IsRunned = false;
if (_player != null && _player.Playing)
{
_player.Stop();
_player.Dispose();
_player = null;
}
stopTimer(ref _debugTimer);
}
private void startDebugTimer()
{
initAndStartTimer(ref _debugTimer, 5,
() => _logger.Value.Info("Background", "Background is fine!"));
}
private void initAndStartTimer(ref NSTimer timer, int intervalMin, Action action)
{
timer?.Invalidate();
if (action != null)
timer =
NSTimer.CreateRepeatingScheduledTimer(
TimeSpan.FromMinutes(intervalMin),
t => action());
}
private void stopTimer(ref NSTimer timer)
{
timer?.Invalidate();
timer = null;
}
}
#endregion
TouchLogger - send some messages to console, for checking background, you can remove it. Same with _debugTimer, it just write state of background worker avery 5 min.
If something not working tell me, i did it long time ago, so maybe missed something
Related
I want make an apps that play audio and video. I want the audio can play on background mode, so I add the MediaPlayerInfo. While I do not want the video played on background mode. So I do not need the MediaPlayerInfo while play video.
But somehow the MediaPlayerInfo still shown in notification bar while I play the video, although I didn't set it.
I already try to set is as "null" but it didn't change the behavior.
Anyone can help me to make the MediaPlayerInfo shown while play audio in background mode, but did not shown while play video (background & foreground mode)?
(Update) this is snippet of my code:
private MPRemoteCommandCenter m_commandCenter = MPRemoteCommandCenter.Shared;
private string m_currentAudioTitle = "";
private MPNowPlayingInfo _currentFileInfo;
NSObject _playCommandTarget;
NSObject _pauseCommandTarget;
NSObject _skipBackwardCommandTarget;
NSObject _skipForwardCommandTarget;
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
m_observerList.Add(NSNotificationCenter.DefaultCenter.AddObserver(aName: UIApplication.DidEnterBackgroundNotification, notify: HandleDidEnterBackgroundNotification));
m_observerList.Add(NSNotificationCenter.DefaultCenter.AddObserver(aName: UIApplication.WillEnterForegroundNotification, notify: HandleWillEnterForegroundNotification));
}
public override void ViewWillDisappear(bool animated)
{
base.ViewWillDisappear(animated);
foreach (NSObject _observer in m_observerList)
{
NSNotificationCenter.DefaultCenter.RemoveObserver(_observer);
}
}
private void HandleWillEnterForegroundNotification(NSNotification obj)
{
RemoveRemoteTransportControl();
RemoveNowPlaying();
}
private void HandleDidEnterBackgroundNotification(NSNotification obj)
{
SetupRemoteTransportControl();
SetupNowPlaying("Test Asset Name");
}
public void SetupRemoteTransportControl()
{
m_commandCenter.SkipBackwardCommand.Enabled = true;
m_commandCenter.SkipForwardCommand.Enabled = true;
_playCommandTarget = m_commandCenter.PlayCommand.AddTarget(PlayAudioBackground);
_pauseCommandTarget = m_commandCenter.PauseCommand.AddTarget(PauseAudioBackground);
_skipBackwardCommandTarget = m_commandCenter.SkipBackwardCommand.AddTarget(SkipBackwardBackground);
_skipForwardCommandTarget = m_commandCenter.SkipForwardCommand.AddTarget(SkipForwardBackground);
}
public void RemoveRemoteTransportControl()
{
m_commandCenter.SkipBackwardCommand.Enabled = false;
m_commandCenter.SkipForwardCommand.Enabled = false;
m_commandCenter.PlayCommand.RemoveTarget(_playCommandTarget);
m_commandCenter.PauseCommand.RemoveTarget(_pauseCommandTarget);
m_commandCenter.SkipBackwardCommand.RemoveTarget(_skipBackwardCommandTarget);
m_commandCenter.SkipForwardCommand.RemoveTarget(_skipForwardCommandTarget);
m_commandCenter.Dispose();
}
public void SetupNowPlaying(string aAssetTitle = null)
{
_currentFileInfo = new MPNowPlayingInfo();
if (!string.IsNullOrEmpty(aAssetTitle))
{
m_currentAudioTitle = aAssetTitle;
}
Console.WriteLine("SetupNowPlaying: "+ m_currentAudioTitle);
_currentFileInfo.Title = m_currentAudioTitle;
_currentFileInfo.ElapsedPlaybackTime = apPlayer.CurrentTime.Seconds;
_currentFileInfo.PlaybackDuration = m_audioAsset.Duration.Seconds;
MPNowPlayingInfoCenter.DefaultCenter.NowPlaying = _currentFileInfo;
}
public void RemoveNowPlaying()
{
MPNowPlayingInfoCenter.DefaultCenter.NowPlaying = _currentFileInfo;
}
Exception
Realm access from incorrect thread in MainViewModel
Application Flow
SplashScreen> MainActivity(Exception)
[Activity(MainLauncher = true
, Icon = "#mipmap/ic_launcher"
, Theme = "#style/Theme.Splash"
, NoHistory = true
, ScreenOrientation = ScreenOrientation.Portrait)]
public class SplashScreen : MvxSplashScreenActivity
{
public SplashScreen()
: base(Resource.Layout.SplashScreen)
{
}
}
MainActivity
public class MainActivity : MvxAppCompatActivity<MainViewModel>,ViewPager.IOnPageChangeListener,View.IOnTouchListener
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.activity_tutorial);
if (ViewModel.IsCompletedOrNot)
ViewModel.OpenMainViewModel.Execute();
}
MainViewModel
[AddINotifyPropertyChangedInterface]
public class MainViewModel : MvxViewModel
{
private Realm _realm;
private bool isCompleted = false;
public TutorialViewModel(IMvxNavigationService navigationService)
{
_realm = Mvx.Resolve<Realm>();
}
public bool IsCompletedOrNot{
get
{
if (_realm.All<IsFirstTimeAppStartUpRealm>().Count() > 0)
{
isCompleted=true;
}else{
isCompleted = false;
}
return isCompleted;
}
}
}
App.CS
var key = ConfigManager.Settings?.DatabaseEcryption?.EncryptionKey;
if (key != null && key.Length > 0)
{
config.EncryptionKey = key;
}
Mvx.RegisterSingleton<Realm>(() => Realm.GetInstance(config));
Realm _realm = Mvx.Resolve<Realm>();
int count = _realm.All<IsFirstTimeAppStartUpRealm>().Count();
//RegisterCustomAppStart<CustomAppStart>();
// App start
if (count>0)
{
RegisterNavigationServiceAppStart<MainViewModel>();
}
else
{
RegisterNavigationServiceAppStart<OtherViewModel>();
}
The below line throws the exception.
_realm.All<IsFirstTimeAppStartUpRealm>().Count() > 0
App always crashes when it comes through SplashScreen and it works fine if started from MainActivity.
MvvmCross does not guarantee that App start is run on the UI thread. I will most likely run on a ThreadPool Thread.
In order to marshal a piece of code to the main thread, you can resolve the IMvxMainThreadAsyncDispatcher (>= 6.1.x) or IMvxMainThreadDispatcher and request an Action to run on the main thread:
var dispatcher = Mvx.Resolve<IMvxMainThreadAsyncDispatcher>();
int count;
await dispatcher.ExecuteOnMainThreadAsync(() =>
{
count = _realm.All<IsFirstTimeAppStartUpRealm>().Count();
});
I have a reasonably nice way of removing these code smells from my applications. I've just recently started using Realm (liking it so far), but I have always used ReactiveProperty to notify my view layer of VM changes - and it's really nice.
https://github.com/runceel/ReactiveProperty
ReactiveProperty is a Rx framework for .NET, which wraps your properties in an instance that produces your INotifyPropertyChanged events as needed. You chain all these properties together as they depend on each other, and events propagate throughout them. You no longer have these long lists of "notify of this, notify of that" after a property change.
Instead you declare how all your members inter-depend, in a single section of your code (typically your constructor)
As a result, you can place the root of the chain on the Realm thread, and all dependent notifications are published on that thread.
So, my ViewModels look something like this (pseudocode):
class VM
{
public ReactiveProperty<AppStartInfo> Entity { get; set; }
public ReactiveProperty<bool> IsFirstLaunch { get; set; }
public VM(){
var syncCtx = SynchronizationContext.Current;
Entity = new ReactiveProperty<AppStartInfo>();
// this property will fire its notifications on the syncCtx.
// remember to bind your view to "IsFirstLaunch.Value"
IsFirstLaunch = Entity.SubscribeOn(syncCtx).Select(x => x.IsFirstLaunch).ToReactiveProperty()
}
public async Task Init()
{
// let's get our realm instance on to the syncCtx.
syncCtx.Post(() => {
Entity.Value = Realm.Find(typeof(AppStartInfo), 0); // or whatever you need.
});
}
}
I make and display an clock count down with this code
LabelField time;
long mille=0;
Timer timer=null;TimerTask task=null;
public Timerscreen() {
mille=1000*60*1;
time=new LabelField();
add(time);
timer=new Timer();
task=new TimerTask() {
public void run() {
synchronized (UiApplication.getEventLock()) {
if(mille!=0){
SimpleDateFormat date=new SimpleDateFormat("mm:ss") ;
System.out.println("================="+date.formatLocal(mille)+"====================="+Thread.activeCount());
time.setText(date.formatLocal(mille));
mille=mille-1000;
}else{
time.setText("00:00");
mille=1000*60*1;
timer.cancel();
UiApplication.getUiApplication().invokeLater(new Runnable() {
public void run() {
Dialog.inform("Time expaired");
}
});
}
}
}
};
timer.schedule(task,0, 1000);
And when I push a new screen , I want to this clock still display and count down.
How can I do that ?
It is not possible to add a single ui field or manager into two managers or screens.. every ui field or manager must have at most one parent (screen or manager).
So if you need a LabelField which will hold and show time on different screens, then you only need to implement some sort of listener which will listen for the time changes.. and for every changes you have to update the screen and the LabelField with the new value. You have already implemented a TimerTask which will provide you updated data.
[Edited - added later]
you can check the following codes, not tested but something like this will solve your problem...
class MyTimerUtil {
TimerListener listener = null;
public MyTimerUtil() {
}
public void setTimerListener(TimerListener listener) {
this.listener = listener;
}
public void startTimer() {
final int interval = 1000;
Timer timer = new Timer();
TimerTask task = new TimerTask() {
public void run() {
// add your codes..
// notify others
if (listener != null) {
listener.timeChanged();
}
}
};
timer.schedule(task, 0, interval);
}
}
interface TimerListener {
public void timeChanged();
}
class ScreeA extends MainScreen implements TimerListener {
public void timeChanged() {
// add Codes here on time changed event
}
}
in the above snippet, you can implement TimerListener interface in any screen instance and can get update on every time changed event by the MyTimerUtil class. For that, you have to set an instance of ScreeA (which implements TimerListener) via setTimerListener() of the MyTimerUtil class.
Also need to start the timer by calling startTimer() method.
I've add timer to display images in my app.
is there any way to check the timer is running or not.?
after checking , the timer should be cancel using timer.cancel() method.
Pls hlp me.
Blackberry's Timer is very cheesy - it's just like a Runnable with Thread.sleep() inside. Very commonly for Blackberry, it contains lot of crap you don't actually need and doesn't contain things you do need.
I would dump the Timer and make a class specially for my needs:
abstract public class MyTimer extends Thread {
private final Object waitobj = new Object();
private volatile boolean running;
private volatile boolean canceled;
private final long due;
public MyTimer setDelay(long delay) {
long cur = System.currentTimeMillis();
due = cur + delay;
return this;
}
public MyTimer setAlarmTime(long dueTimeMillis) {
due = dueTimeMillis;
return this;
}
synchronized void setIsRunning(boolean running) {
this.running = running;
}
synchronized public boolean isRunning() {
return running;
}
synchronized public void cancel() {
synchronized (waitobj) {
canceled = true;
waitobj.notify();
}
}
public void run() {
setIsRunning(true);
long cur = System.currentTimeMillis();
long sleep = due - cur;
while (sleep > 0) {
synchronized (waitobj) {
waitobj.wait(sleep);
}
if (isCanceled()) return;
cur = System.currentTimeMillis();
sleep = due - cur;
}
alarm();
setIsRunning(false);
}
private boolean isCanceled() {
return canceled;
}
abstract void alarm();
}
Then I would invoke it like this:
timer = new MyTimer() {
void alarm() {
// do cool things
}
};
timer.setDelay(10000).start();
If I need to cancel it I would do it like this:
if (timer.isRunning()) {
timer.cancel();
}
or simply
timer.cancel();
PS: Note volatile and synchronized things in MyTimer class.
You can manage this yourself by recording the timers unique integer and use it later to cancel. I find a useful place to set/cancel this is in the onVisibilityChanged(boolean) override. I'm assuming here your timed images are for animation.
// start
if (renderLoop==-1) renderLoop = UiApplication.getUiApplication().invokeLater( this, 50, true );
// stop
if (renderLoop!=-1)
{
UiApplication.getUiApplication().cancelInvokeLater( renderLoop );
renderLoop = -1;
}
//assumes your screen implements Runnable
public void run() {
// do something cool
}
Assuming that you're programming in Java with Swing, you have a method called isRunning() within the Timer Class.
http://download.oracle.com/javase/7/docs/api/javax/swing/Timer.html#isRunning%28%29
Regards
I have a BlackBerry application that needs to take pictures from the camera and send them to a server. In order to do this i invoke the native camera application and listen to the filesystem. Once an image is captured and saved as a new jpeg file i get notified, resume foreground control and go about my business. The problem starts occurring after the first time this cycle is completed because now when i decide to call the camera application again it is already opened, and now the user is seeing a thumbnail of the last picture that was taken and several buttons allowing him to manipulate/manage it. naturally what i want the user to see is a preview of what the camera is "seeing" before he snaps another photo as he did before.
I have thought of various ways to solve this including killing the camera app each time (I understand this cannot be done programatically?), sending CameraArguments when invoking the app (which appears to be useless), and now i was thinking a solution could be as simple generating a "Back" key event before switching back to my app which would theoretically dismiss the annoying edit screen. Could this really be done? and if not is there any other possible solution you may think of?
A kind of hack...
start Camera App
in TimerTask check if Camera App started and if it need to be closed (some flag)
if yes, invoke it(so it will became active) and push ESC keypress event injection to close it
Take a look at this:
class Scr extends MainScreen {
boolean killCameraApp = false;
final String mCameraModuleName = "net_rim_bb_camera";
final CameraArguments args = new CameraArguments();
public Scr() {
super();
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
if (isCameraRunning() && killCameraApp) {
getApplication().invokeAndWait(callCamera);
getApplication().invokeAndWait(killCamera);
}
}
}, 0, 100);
}
Runnable callCamera = new Runnable() {
public void run() {
callCamera();
}
};
Runnable killCamera = new Runnable() {
public void run() {
injectKey(Characters.ESCAPE);
killCameraApp = false;
}
};
private boolean isCameraRunning() {
boolean result = false;
ApplicationManager appMan =
ApplicationManager.getApplicationManager();
ApplicationDescriptor[] appDes = appMan.getVisibleApplications();
for (int i = 0; i < appDes.length; i++) {
result = mCameraModuleName.equalsIgnoreCase(appDes[i]
.getModuleName());
if (result)
break;
}
return result;
}
private void callCamera() {
Invoke.invokeApplication(Invoke.APP_TYPE_CAMERA,
new CameraArguments());
}
private void injectKey(char key) {
KeyEvent inject = new KeyEvent(KeyEvent.KEY_DOWN, key, 0);
inject.post();
}
protected void makeMenu(Menu menu, int instance) {
menu.add(new MenuItem("start camera", 0, 0) {
public void run() {
callCamera();
killCameraApp = false;
}
});
menu.add(new MenuItem("kill app", 0, 0) {
public void run() {
killCameraApp = true;
}
});
super.makeMenu(menu, instance);
}
}
EDIT: Don't forget to set permissions for device release:
Options => Advanced Options => Applications => [Your Application] =>Edit Default permissions =>Interactions =>key stroke Injection