I want to update my live data(s) in my ViewModel when the app detects the user's filters changed on a drawer layout close listener. I have created an update all live data method in my ViewModel, but it doesn't seem to work.
Here's my ViewModel:
public class ReleasesViewModel extends ViewModel {
private HashMap<String, MutableLiveData<List<_Release>>> upcomingReleasesListMap = new HashMap<>();
private ReleasesRepository releasesRepository;
private ArrayList<Integer> platforms;
private ArrayList<Integer> platformsCopy;
private String region;
public ReleasesViewModel() {
upcomingReleasesListMap = new HashMap<>();
// Shared to all fragments : User settings region & platforms
region = SharedPrefManager.read(SharedPrefManager.KEY_PREF_REGION, "North America");
Set<String> defaultPlatformsSet = new HashSet<>();
platforms = SharedPrefManager.read(SharedPrefManager.PLATFORM_IDS);
if (platformsCopy == null) {
// Set only once [past copy of platforms]
platformsCopy = SharedPrefManager.read(SharedPrefManager.PLATFORM_IDS);
}
}
public MutableLiveData<List<_Release>> getUpcomingReleases(String filter) {
// ReleasesRepository takes a different monthly filter
if (upcomingReleasesListMap.get(filter) == null) {
// we don't have a mapping for this filter so create one in the map
MutableLiveData<List<_Release>> releases = new MutableLiveData<>();
upcomingReleasesListMap.put(filter, releases);
// also call this method to update the LiveData
loadReleases(filter);
} else if (upcomingReleasesListMap.containsKey(filter)) {
// Double check if it isn't null, just in case
if (upcomingReleasesListMap.get(filter) == null || isPlatformsUpdated()) {
// if null; try again to update the live data or if platforms filter changed
loadReleases(filter);
} // else just don't do anything, the list is already in the Map
}
return upcomingReleasesListMap.get(filter);
}
private void loadReleases(final String filter) {
releasesRepository = new ReleasesRepository(region, filter, platforms);
releasesRepository.addListener(new FirebaseDatabaseRepository.FirebaseDatabaseRepositoryCallback<_Release>() {
#Override
public void onSuccess(List<_Release> result) {
// sort by release date
if (platforms.size() > 1) {
// Will only sort for multiple platforms filter
Collections.sort(result);
}
// just use the previous created LiveData, this time with the data we got
MutableLiveData<List<_Release>> releases = upcomingReleasesListMap.get(filter);
releases.setValue(result);
}
#Override
public void onError(Exception e) {
// Log.e(TAG, e.getMessage());
MutableLiveData<List<_Release>> releases = upcomingReleasesListMap.get(filter);
releases.setValue(null);
}
});
}
// Detects when user added/removed platform to update lists based on platforms
private boolean isPlatformsUpdated() {
Collections.sort(platforms);
Collections.sort(platformsCopy);
if (platforms.equals(platformsCopy)) {
// nothing new added since past update
return false;
}
// something new added or removed, change
platformsCopy = SharedPrefManager.read(SharedPrefManager.PLATFORM_IDS);
return true;
}
public void updateAllReleasesList() {
// update all releases live data lists
for (String filter : upcomingReleasesListMap.keySet()) {
loadReleases(filter);
}
}
}
The updateAllReleasesList is the method I created to update all my livedata lists, but in calling this method it will call the loadReleases method again and inside this method, it will skip the entire listener addListener code for some reason.
In my fragments where I listen to the data changes I have this following simple observer:
mReleasesViewModel = ViewModelProviders.of(getActivity()).get(ReleasesViewModel.class);
mReleasesViewModel.getUpcomingReleases(filter).observe(this, new Observer<List<_Release>>() {
#Override
public void onChanged(#Nullable List<_Release> releases) {
// whenever the list is changed
if (releases != null) {
mUpcomingGamesAdapter.setData(releases);
mUpcomingGamesAdapter.notifyDataSetChanged();
Toast.makeText(getContext(), "Updated", Toast.LENGTH_SHORT).show();
}
mDatabaseLoading.setVisibility(View.GONE);
}
});
And when I call my update all method in my ViewModel on drawer close, the code inside the observer in my fragment gets called (the list returned is empty), but I want it to call getUpcomingReleases to update everything.. Any ideas on how to update all my current livedatas at the same time and reflect it on the UI?
Having set a system property in android using the setprop command (through adb) is there a way to listen to this change in my own service?
I tried with SystemProperties.addChangeCallback and was not notified. Was there something that I missed?
You can create a method in your service which should fetch any Systemproperty and that method should call Looper.loop(); so that that loop will poll for SystemProperty time to time
This implementation may not be optimized way of doing this but it is used in Android 4.4.2, you can see here http://androidxref.com/4.4.2_r2/xref/frameworks/base/services/java/com/android/server/SystemServer.java
you can see at above link:
boolean disableStorage = SystemProperties.getBoolean("config.disable_storage", false);
boolean disableMedia = SystemProperties.getBoolean("config.disable_media", false);
boolean disableBluetooth = SystemProperties.getBoolean("config.disable_bluetooth", false);
boolean disableTelephony = SystemProperties.getBoolean("config.disable_telephony", false);
boolean disableLocation = SystemProperties.getBoolean("config.disable_location", false);
boolean disableSystemUI = SystemProperties.getBoolean("config.disable_systemui", false);
boolean disableNonCoreServices = SystemProperties.getBoolean("config.disable_noncore", false);
boolean disableNetwork = SystemProperties.getBoolean("config.disable_network", false);
These boolean variables are being checked in initAndLoop() method with the help of Looper.loop(); here you can notify your other components on any change in even a single SystemProperty.
Another way is to create static callback and get call for any change in any of SystemProperty, see the master branch's code for SystemService here: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/os/SystemService.java
you can see in above link what following code is doing:
private static Object sPropertyLock = new Object();
static {
SystemProperties.addChangeCallback(new Runnable() {
#Override
public void run() {
synchronized (sPropertyLock) {
sPropertyLock.notifyAll();
}
}
});
}
/**
* Wait until given service has entered specific state.
*/
public static void waitForState(String service, State state, long timeoutMillis)
throws TimeoutException {
final long endMillis = SystemClock.elapsedRealtime() + timeoutMillis;
while (true) {
synchronized (sPropertyLock) {
final State currentState = getState(service);
if (state.equals(currentState)) {
return;
}
if (SystemClock.elapsedRealtime() >= endMillis) {
throw new TimeoutException("Service " + service + " currently " + currentState
+ "; waited " + timeoutMillis + "ms for " + state);
}
try {
sPropertyLock.wait(timeoutMillis);
} catch (InterruptedException e) {
}
}
}
}
/**
* Wait until any of given services enters {#link State#STOPPED}.
*/
public static void waitForAnyStopped(String... services) {
while (true) {
synchronized (sPropertyLock) {
for (String service : services) {
if (State.STOPPED.equals(getState(service))) {
return;
}
}
try {
sPropertyLock.wait();
} catch (InterruptedException e) {
}
}
}
}
This information originates from Shridutt Kothari. Check this google post about listening to single SystemProperty changes
Too long for comments, so adding as an answer:
The setprop tool does not appear to fire change callbacks. In my read of the OS source, it simply sets a property value in a hashmap (see: https://cs.android.com/android/platform/superproject/+/master:system/libbase/properties.cpp;bpv=1;bpt=1;l=133?q=setprop).
For the callback to happen, someone needs to call do_report_sysprop_change (https://cs.android.com/android/platform/superproject/+/master:system/core/libutils/misc.cpp;bpv=1;bpt=1;l=102?q=syspropchange&ss=android%2Fplatform%2Fsuperproject&gsn=do_report_sysprop_change&gs=kythe%3A%2F%2Fandroid.googlesource.com%2Fplatform%2Fsuperproject%3Flang%3Dc%252B%252B%3Fpath%3Dsystem%2Fcore%2Flibutils%2Fmisc.cpp%23tC5_BHx-Z-jKUw_rRXWlL0wtVyVh15Oh60E0YnrdfSg&gs=kythe%3A%2F%2Fandroid.googlesource.com%2Fplatform%2Fsuperproject%3Flang%3Dc%252B%252B%3Fpath%3Dsystem%2Fcore%2Flibutils%2Fmisc.cpp%234FfZ9jgPIUEg7IKCGYGLCaQ4-cj6enFs8AeI7SIRLBs) and this is not done via setprop. I do see it invoked in a variety of places in the OS since invocation of the set prop callback is a method in the IBase interface implemented by a variety of services in Android.
I am working on a project where I want to upload files after developer authentication is complete. I am using AWS Cognito for authentication. Problem here is sometimes TransferUtility does not trigger onProgresschanged. Although it does not trigger onprogresschanged but the file is getting uploaded. I want to show a progressbar on the UI for every upload.It is working sometimes and sometimes it is not working.
Here is how I am uploading files.
public void upload() {
ClientConfiguration configuration = new ClientConfiguration();
configuration.setProtocol(Protocol.HTTP);
configuration.setSocketTimeout(5 * 10000);
configuration.setConnectionTimeout(5 * 10000);
configuration.setMaxErrorRetry(3);
if(sS3Client==null) {
sS3Client = new AmazonS3Client(credentials,configuration);
}
sTransferUtility = new TransferUtility(sS3Client,
this.ctx);
observer = sTransferUtility.upload("bucketer", "Filename", "file");
observer.setTransferListener(new UploadListener(progress));
}
private class UploadListener implements TransferListener {
ProgressBar progressBar;
public UploadListener(ProgressBar progress){
this.progressBar = progress;
}
#Override
public void onStateChanged(int i, TransferState transferState) {
Log.d("STATUS CHANGED:".concat(String.valueOf(i)),transferState.toString());
switch (transferState.toString())
{
case "IN_PROGRESS":
{
Log.d("IN_PROGRESS", "IN_PROGRESS");
}
break;
case "COMPLETED":
{
Log.d("COMPLETED COMPLETED", "COMPLETED");
}
break;
}
}
#Override
public void onProgressChanged(int i, long l, long l1) {
updator();
this.progressBar.setProgress(transferprogres);
}
#Override
public void onError(int i, Exception e) {
Log.d("UPOLADING ERROR:",String.valueOf(e));
}
}
public void updator(){
transferprogres = (int) ((double) observer.getBytesTransferred() * 100 / observer.getBytesTotal());
}
The code above is a part of total project. For more details comment.
Why is it showing weird performance?
See Aws S3 TransferService Upload failing without errors and https://github.com/aws/aws-sdk-android/issues/101. In short, v2.2.12 requires user to manage the life cycle of transfer listeners as transfer utility keeps only weak references of them. You can make the listener or the observer as class variable to prevent it from garbage collected. Anyway, we are tweaking the listeners in future releases. Sorry for the trouble and please stay tuned.
My android application has a FeedDetailFragment that displays Feed details. A feed has basic information and metadata, which are retrieved through two separate calls to the server. The server interface is filled in with Retrofit. I have implemented something that, to my novice Rx knowledge, looks logical. However, as you may have guessed, it doesn't work.
External classes:
FeedInfo - parcellable class that contains basic feed info
FeedMetadata - parcellable class that contains metadata about feed
Feed - auxiliary class that combines feed info and metadata, providing some hepler functions
UvClient - server interface implemented with Retrofit
Relevant FeedDetailFragment code:
public class FeedDetailFragment extends Fragment implements OnMapReadyCallback {
public static final String ARG_FEED_ID = "feed_id";
public static final String ARG_FEED_INFO = "feed_info";
public static final String ARG_FEED_METADATA = "feed_metadata";
public static final int INVALID_FEED_ID = -1;
...
private class PlaceFeedSubscriber extends Subscriber<Pair<GoogleMap, Feed>> {
#Override
public void onNext(Pair<GoogleMap, Feed> pair) {
Log.i(TAG, String.format("Placing feed %d on [%f, %f] onto map %s",
pair.second.getInfo(),
pair.second.getMetadata().getSensorLatitude(),
pair.second.getMetadata().getSensorLongitude(),
pair.first.getMapType()));
pair.first.addMarker(new MarkerOptions()
.position(new LatLng(
pair.second.getMetadata().getSensorPoint().getCoordinates()[1],
pair.second.getMetadata().getSensorPoint().getCoordinates()[0]))
.title("Marker"));
mMapAPI.moveCamera(CameraUpdateFactory.newLatLngZoom(
new LatLng(
pair.second.getMetadata().getSensorPoint().getCoordinates()[1],
pair.second.getMetadata().getSensorPoint().getCoordinates()[0])
, 15));
}
#Override
public void onCompleted() {
Log.i(TAG, "Completed drawing of feed");
}
#Override
public void onError(Throwable e) {
Log.e(TAG, "Drawing of feed failed with: " + e);
}
}
public FeedDetailFragment() {
mMapObservable = Observable.empty().subscribeOn(Schedulers.io());
mFeedIdObservable = Observable.empty().subscribeOn(Schedulers.io());
mFeedInfoObservable = Observable.empty();
mFeedMetadataObservable = Observable.empty();
// Start fetching new feed information
mFeedIdObservable.doOnEach(new Action1<Integer>() {
#Override
public void call(Integer feedId) {
Log.d(TAG, "Got a new feed id - " + feedId);
mFeedInfoObservable.mergeWith(mUvClient.getFeed(feedId));
}
});
// Start fetching new feed metadata
mFeedInfoObservable.doOnEach(new Action1<FeedInfo>() {
#Override
public void call(FeedInfo feedInfo) {
Log.d(TAG, "Got a new feed info - " + feedInfo.getTitle());
mFeedMetadataObservable.mergeWith(mUvClient.getFeedMetadata(feedInfo.getId()));
}
});
// Produce a new feed
mFeedObservable = Observable.combineLatest(mFeedInfoObservable, mFeedMetadataObservable, new Func2<FeedInfo, FeedMetadata, Feed>() {
#Override
public Feed call(FeedInfo feedInfo, FeedMetadata feedMetadata) {
return new Feed(feedInfo, feedMetadata);
}
});
// Render the feed onto map
Observable.combineLatest(mFeedObservable, mMapObservable, new Func2<Feed, GoogleMap, Pair<GoogleMap, Feed>>() {
#Override
public Pair<GoogleMap, Feed> call(Feed feed, GoogleMap map) {
return new Pair(map, feed);
}
}).observeOn(AndroidSchedulers.mainThread())
.subscribe(new PlaceFeedSubscriber());
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle arguments = getArguments();
if (arguments.containsKey(ARG_FEED_ID)) {
setFeed(arguments.getInt(ARG_FEED_ID));
}
else if (arguments.containsKey(ARG_FEED_INFO)) {
if (arguments.containsKey(ARG_FEED_METADATA)) {
setFeed((FeedInfo)Parcels.unwrap(arguments.getParcelable(ARG_FEED_INFO)),
(FeedMetadata)Parcels.unwrap(arguments.getParcelable(ARG_FEED_METADATA)));
}
else {
setFeed((FeedInfo)Parcels.unwrap(arguments.getParcelable(ARG_FEED_INFO)));
}
}
}
...
#Override
public void onMapReady(GoogleMap googleMap) {
mMapAPI = googleMap;
mMapObservable.mergeWith(Observable.just(googleMap));
}
/**
* Sets the feed ID to be shown in the fragment. This triggers the chain of fetching feed info
* and feed metadata, finally displaying it on the map.
* #param feedId ID of the feed to display in the fragment.
*/
public void setFeed(int feedId) {
Log.d(TAG, String.format("Setting new feed ID - %d", feedId));
mFeedIdObservable.mergeWith(Observable.just(feedId));
}
/**
* Sets feed info. This triggers fetching of feed metadata, finally displaying it on the map.
* #param feedInfo Information of the feed to display on the map.
*/
public void setFeed(FeedInfo feedInfo) {
Log.d(TAG, String.format("Setting new feed info - %s", feedInfo.getTitle()));
mFeedInfoObservable.mergeWith(Observable.just(feedInfo));
}
/**
* Displays feed info on the map.
* #param feedInfo Information of the feed to display on the map.
* #param feedMetadata Metadata of the feed to display on the map.
*/
public void setFeed(FeedInfo feedInfo, FeedMetadata feedMetadata) {
Log.d(TAG, String.format("Setting new feed info and metadata - %s", feedInfo.getTitle()));
mFeedObservable.mergeWith(Observable.just(new Feed(feedInfo, feedMetadata)));
}
}
The log output I see is as follows:
Setting new feed info - SampleFeed
Completed drawing of feed
My overall idea was that the observables would emit new data when I merge it in. Some observables are created empty so that they do not emit anything but I can still work with them.
The potential flow could be as follows:
Activity gets a callback from FeedListFragment notifying the id of the feed that was clicked
Activity checks either gets and passes FeedInfo to FeedDetailFragment.setFeed or invokes FeedDetailFragment.setFeed with the feed's id (lets assume the latter for completeness)
FeedDetailFragment merges new observable with the received feed id
The merge triggers emission of new event on mFeedIdObservable
The .doOnEach of mFeedIdObservable kicks off Retrofit interface to fetch FeedInfo
The .doOnEach of mFeedInfoObservable kick off Retrofit interface to fetch FeedMetadata
The .combineLatest of mFeedObservable fires off when both mFeedInfoObservable and mFeedMetadataObservable return new data
Finally, getting the GoogleMap and Feed, the call is made to subscriber to draw the feed on the map
This is how the thought was put together in my head. Obviously, it is wrong. Where did I go wrong and how can I fix it? I'd love some pointers and maybe more general ideology/methodology approach teaching. Thanks for any advice!
UPDATE 1
So I've been trying to figure this out. Read more documents... a lot to learn. I've replaced Observable.empty() with Observable.never(). From documentation, I read that empty doesn't emit anything and completes, which is not what I want. On the other hand, never doesn't emit anything but does not complete. As such, I can use it for the purpose I'm seeking. Still not getting what I want but, I hope, one step closer.
UPDATE 2
Getting a bit more hang of it. Looking into source of .never() and .empty(), I see that the former does not call .onNext() and the latter calls .onComplete(). There is nothing in the middle I can choose. Started looking around for alternatives. Basically, my code doesn't execute because, in my previous tries, observable either completed immediately or never proceeded to call next. However, there is nothing to call .onNext() in the beginning. As such, I need a placeholder.
Reading more docs, I came across Subjects. In particular, PublishSubject doesn't emit anything until a subscriber subscribes. This seemed like a viable solution. However, the subscriber must subscribe directly to the subject. This didn't seem to work with .mergeWith() upon the subject.
Will not give up :)
UPDATE 3
Thanks to #dwursteisen, I continued with PublishSubject approach. This is the relevant code that changed:
...
private PublishSubject<GoogleMap> mMapObservable = null;
private PublishSubject<Feed> mFeedObservable = null;
private PublishSubject<Integer> mFeedIdObservable = null;
private PublishSubject<FeedInfo> mFeedInfoObservable = null;
private PublishSubject<FeedMetadata> mFeedMetadataObservable = null;
...
public FeedDetailFragment() {
mMapObservable = PublishSubject.create();
mFeedObservable = PublishSubject.create();
mFeedIdObservable = PublishSubject.create();
mFeedInfoObservable = PublishSubject.create();
mFeedMetadataObservable = PublishSubject.create();
mMapObservable.subscribe(new Action1<GoogleMap>() {
#Override
public void call(GoogleMap googleMap) {
mMapApi = googleMap;
}
});
mFeedMetadataObservable.subscribe(new Action1<FeedMetadata>() {
#Override
public void call(FeedMetadata feedMetadata) {
// no code
}
});
mFeedObservable.subscribe(new Action1<Feed>() {
#Override
public void call(Feed feed) {
// no code
}
});
// Start fetching new feed information
mFeedIdObservable.subscribe(new Action1<Integer>() {
#Override
public void call(Integer feedId) {
mUvClient.getFeed(feedId).subscribe(new Action1<FeedInfo>() {
#Override
public void call(FeedInfo feedInfo) {
mFeedInfoObservable.onNext(feedInfo);
}
});
}
});
// Start fetching new feed metadata
mFeedInfoObservable
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<FeedInfo>() {
#Override
public void call(FeedInfo feedInfo) {
mFeedTitle.setText(feedInfo.getTitle());
mUvClient.getFeedMetadata(feedInfo.getId()).subscribe(new Action1<FeedMetadata>() {
#Override
public void call(FeedMetadata feedMetadata) {
mFeedMetadataObservable.onNext(feedMetadata);
}
});
}
});
// Produce a new feed
Observable.combineLatest(mFeedInfoObservable, mFeedMetadataObservable, new Func2<FeedInfo, FeedMetadata, Feed>() {
#Override
public Feed call(FeedInfo feedInfo, FeedMetadata feedMetadata) {
Feed feed = new Feed(feedInfo, feedMetadata);
return feed;
}
}).subscribeOn(Schedulers.io()).subscribe(new Action1<Feed>() {
#Override
public void call(Feed feed) {
mFeedObservable.onNext(feed);
}
});
// Render the feed onto map
Observable.combineLatest(mFeedObservable, mMapObservable, new Func2<Feed, GoogleMap, Pair<GoogleMap, Feed>>() {
#Override
public Pair<GoogleMap, Feed> call(Feed feed, GoogleMap map) {
return new Pair(map, feed);
}
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(new PlaceFeedSubscriber());
}
...
#Override
public void onMapReady(GoogleMap googleMap) {
mMapObservable.onNext(googleMap);
}
/**
* Sets the feed ID to be shown in the fragment. This triggers the chain of fetching feed info
* and feed metadata, finally displaying it on the map.
* #param feedId ID of the feed to display in the fragment.
*/
public void setFeed(int feedId) {
mFeedIdObservable.onNext(feedId);
}
/**
* Sets feed info. This triggers fetching of feed metadata, finally displaying it on the map.
* #param feedInfo Information of the feed to display on the map.
*/
public void setFeed(FeedInfo feedInfo) {
mFeedInfoObservable.onNext(feedInfo);
}
/**
* Displays feed info on the map.
* #param feedInfo Information of the feed to display on the map.
* #param feedMetadata Metadata of the feed to display on the map.
*/
public void setFeed(FeedInfo feedInfo, FeedMetadata feedMetadata) {
mFeedObservable.onNext(new Feed(feedInfo, feedMetadata));
}
Obviously, now that I got the basics working, I'll go through it and do proper handling of errors, caching, and other conditions. However, I do have one question: is there any way to simplify the following code to directly use Retrofit observable instead of subscribing to it inside the subscribe... maybe Rx operator that would inject its resolution into mFeedInfoObservable?
mFeedIdObservable.subscribe(new Action1<Integer>() {
#Override
public void call(Integer feedId) {
mUvClient.getFeed(feedId).subscribe(new Action1<FeedInfo>() {
#Override
public void call(FeedInfo feedInfo) {
mFeedInfoObservable.onNext(feedInfo);
}
});
}
});
Also, I would love to hear any comments in the general approach. I'm still wrapping my head around Rx and my implementation is not best, I am sure.
mFeedInfoObservable = Observable.empty();
You build an empty Observable that will never emit value. So when you'll subscribe to this Observable, you'll be only notified of it's completion.
mFeedInfoObservable.mergeWith(Observable.just(feedInfo));
Observable are immutable. It's mean that calling a method won't change its state. mergeWith will produce a new Observable that is the result of the merge of an Observable with another.
So in your case, you build an new Observable that aren't used.
According to your code, it's seem that you need a Subject (like you mention : PublishSubject) to emit value from different user call.
private final Subject<Integer, Integer> subject = PublishSubject.create();
public void setFeed(int feedId) {
subject.onNext(feedId);
}
public FeedDetailFragment() {
subject.flatMap(feedId -> mUvClient.getFeed(feedId))
.subscribe(/**...**/);
}
Please note that doOnNext should be used for side effect call (ie: code that will change an element outside of your Observable, like logging, ...). I think in your case you may need other operators like flatMap, zip, ... in order to compose the result like what you want to achieve.
I have seen plenty of examples of how to use Android TextToSpeak in an Activity, and have also managed to get this to work just fine. I've also managed to get it to work using a bound service in a plugin, but it seems overcomplicated for my purposes. Here is my VoiceService class:
public class VoiceService : IVoiceService, TextToSpeech.IOnInitListener
{
public event EventHandler FinishedSpeakingEventHandler;
private TextToSpeech _tts;
public void Init()
{
// Use a speech progress listener so we get notified when the service finishes speaking the prompt
var progressListener = new SpeechProgressListener();
progressListener.FinishedSpeakingEventHandler += OnUtteranceCompleted;
//_tts = new TextToSpeech(Application.Context, this);
_tts = new TextToSpeech(Mvx.Resolve<IMvxAndroidCurrentTopActivity>().Activity, this);
_tts.SetOnUtteranceProgressListener(progressListener);
}
public void OnInit(OperationResult status)
{
// THIS EVENT NEVER FIRES!
Console.WriteLine("VoiceService TextToSpeech Initialised. Status: " + status);
if (status == OperationResult.Success)
{
}
}
public void Speak(string prompt)
{
if (!string.IsNullOrEmpty(prompt))
{
var map = new Dictionary<string, string> { { TextToSpeech.Engine.KeyParamUtteranceId, new Guid().ToString() } };
_tts.Speak(prompt, QueueMode.Flush, map);
Console.WriteLine("tts_Speak: " + prompt);
}
else
{
Console.WriteLine("tts_Speak: PROMPT IS NULL OR EMPTY!");
}
}
/// <summary>
/// When we finish speaking, call the event handler
/// </summary>
public void OnUtteranceCompleted(object sender, EventArgs e)
{
if (FinishedSpeakingEventHandler != null)
{
FinishedSpeakingEventHandler(this, new EventArgs());
}
}
public void Dispose()
{
//throw new NotImplementedException();
}
public IntPtr Handle { get; private set; }
}
Note that the OnInit method never gets called.
In my viewmodel I'd like to do this:
_voiceService.Init();
_voiceService.FinishedSpeakingEventHandler += _voiceService_FinishedSpeakingEventHandler;
... and then later ...
_voiceService.Speak(prompt);
When I do this I get these messages in the output:
10-13 08:13:59.734 I/TextToSpeech( 2298): Sucessfully bound to com.google.android.tts
(happens when I create the new TTS object)
and
10-13 08:14:43.924 W/TextToSpeech( 2298): speak failed: not bound to TTS engine
(when I call tts.Speak(prompt))
If I was using an activity I would create an intent to get this to work, but I'm unsure how to do that in a plugin.
Thanks in advance,
David
Don't implement Handle yourself, instead derive from Java.Lang.Object
public class VoiceService : Java.Lang.Object, IVoiceService, TextToSpeech.IOnInitListener
and remove your Dispose() and Handle implementation
More info here: http://developer.xamarin.com/guides/android/advanced_topics/java_integration_overview/android_callable_wrappers/
Also, I suggest you take an async approach when implementing your service, which would make calling it from view-model something like
await MvxResolve<ITextToSpeechService>().SpeakAsync(text);