RxJava- RxAndroid form validation on dynamic EditText - android

I have form that can have variable number of EditText that needs to be validated before form submission. I can perform validation check if EditTexts are fixed in number like following -
Observable<CharSequence> emailObservable = RxTextView.textChanges(editEmail).skip(1);
Observable<CharSequence> passwordObservable = RxTextView.textChanges(editPassword).skip(1);
mFormValidationSubscription = Observable.combineLatest(emailObservable, passwordObservable,
(newEmail, newPassword) -> {
boolean emailValid = !TextUtils.isEmpty(newEmail) && android.util.Patterns.EMAIL_ADDRESS.matcher(newEmail).matches();
if(!emailValid) {
emailInputLayout.setError(getString(R.string.error_invalid_email));
emailInputLayout.setErrorEnabled(true);
}else {
emailInputLayout.setError(null);
emailInputLayout.setErrorEnabled(false);
}
boolean passValid = !TextUtils.isEmpty(newPassword) && newPassword.length() > 4;
if (!passValid) {
passwordInputLayout.setError(getString(R.string.error_invalid_password));
passwordInputLayout.setErrorEnabled(true);
} else {
passwordInputLayout.setError(null);
passwordInputLayout.setErrorEnabled(true);
}
return emailValid && passValid;
}).subscribe(isValid ->{
mSubmitButton.setEnabled(isValid);
});
But now as there are variable number of inputs I tried creating a list of Observable<CharSequence> and Observable.combineLatest() but I'm stuck as to proceed with that.
List<Observable<CharSequence>> observableList = new ArrayList<>();
for(InputRule inputRule : mMaterial.getRules()) {
View vInputRow = inflater.inflate(R.layout.item_material_input_row, null, false);
StyledEditText styledEditText = ((StyledEditText)vInputRow.findViewById(R.id.edit_input));
styledEditText.setHint(inputRule.getName());
Observable<CharSequence> observable = RxTextView.textChanges(styledEditText).skip(1);
observableList.add(observable);
linearLayout.addView(vInputRow);
}
Observable.combineLatest(observableList,......); // What should go in place of these "......"
How can I perform checks for a valid charsequence for each input field. I looked into flatMap(), map(), filter() methods but I don't know how to use them.

Yes, you process abitrary number of Observables in .combineLatest(), but there is still workaround. I got interested in this problem and came up with following solution- we can store information about some data source - last value and source ID (String and resource id) and tunnell all data into some common pipe. For that we can use PublishSubject. We also need to track connection state, for that we should save Subscription to each source on subscription and sever it when we unsubscribe from that source.
We store last data from each source, so we can tell user what source just emitted new value, callback will only contain source id. User can get last value of any source by source id.
I came up with the following code:
import android.util.Log;
import android.widget.EditText;
import com.jakewharton.rxbinding.widget.RxTextView;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import rx.Observable;
import rx.Subscription;
import rx.functions.Action1;
import rx.subjects.PublishSubject;
public class MultiSourceCombinator {
String LOG_TAG = MultiSourceCombinator.class.getSimpleName();
/**
* We can't handle arbitrary number of sources by CombineLatest, but we can pass data along
* with information about source (sourceId)
*/
private static class SourceData{
String data = "";
Integer sourceId = 0;
}
/**
* Keep id of source, subscription to that source and last value emitted
* by source. This value is passed when source is attached
*/
private class SourceInfo{
Subscription sourceTracking;
Integer sourceId;
SourceData lastData;
SourceInfo(int sourceId, String data){
this.sourceId = sourceId;
// initialize last data with empty value
SourceData d = new SourceData();
d.data = data;
d.sourceId = sourceId;
this.lastData = d;
}
}
/**
* We can tunnel data from all sources into single pipe. Subscriber can treat it as
* Observable<SourceData>
*/
private PublishSubject<SourceData> dataDrain;
/**
* Stores all sources by their ids.
*/
Map<Integer, SourceInfo> sources;
/**
* Callback, notified whenever source emit new data. it receives source id.
* When notification is received by client, it can get value from source by using
* getLastSourceValue(sourceId) method
*/
Action1<Integer> sourceUpdateCallback;
public MultiSourceCombinator(){
dataDrain = PublishSubject.create();
sources = new HashMap<>();
sourceUpdateCallback = null;
// We have to process data, ccoming from common pipe
dataDrain.asObservable()
.subscribe(newValue -> {
if (sourceUpdateCallback == null) {
Log.w(LOG_TAG, "Source " + newValue.sourceId + "emitted new value, " +
"but used did't set callback ");
} else {
sourceUpdateCallback.call(newValue.sourceId);
}
});
}
/**
* Disconnect from all sources (sever Connection (s))
*/
public void stop(){
Log.i(LOG_TAG, "Unsubscribing from all sources");
// copy references to aboid ConcurrentModificatioinException
ArrayList<SourceInfo> t = new ArrayList(sources.values());
for (SourceInfo si : t){
removeSource(si.sourceId);
}
// right now there must be no active sources
if (!sources.isEmpty()){
throw new RuntimeException("There must be no active sources");
}
}
/**
* Create new source from edit field, subscribe to this source and save subscription for
* further tracking.
* #param editText
*/
public void addSource(EditText editText, int sourceId){
if (sources.containsKey(sourceId)){
Log.e(LOG_TAG, "Source with id " + sourceId + " already exist");
return;
}
Observable<CharSequence> source = RxTextView.textChanges(editText).skip(1);
String lastValue = editText.getText().toString();
Log.i(LOG_TAG, "Source with id " + sourceId + " has data " + lastValue);
// Redirect data coming from source to common pipe, to do that attach source id to
// data string
Subscription sourceSubscription = source.subscribe(text -> {
String s = new String(text.toString());
SourceData nextValue = new SourceData();
nextValue.sourceId = sourceId;
nextValue.data = s;
Log.i(LOG_TAG, "Source " + sourceId + "emits new value: " + s);
// save vlast value
sources.get(sourceId).lastData.data = s;
// pass new value down pipeline
dataDrain.onNext(nextValue);
});
// create SourceInfo
SourceInfo sourceInfo = new SourceInfo(sourceId, lastValue);
sourceInfo.sourceTracking = sourceSubscription;
sources.put(sourceId, sourceInfo);
}
/**
* Unsubscribe source from common pipe and remove it from list of sources
* #param sourceId
* #throws IllegalArgumentException
*/
public void removeSource(Integer sourceId) throws IllegalArgumentException {
if (!sources.containsKey(sourceId)){
throw new IllegalArgumentException("There is no source with id: " + sourceId);
}
SourceInfo si = sources.get(sourceId);
Subscription s = si.sourceTracking;
if (null != s && !s.isUnsubscribed()){
Log.i(LOG_TAG, "source " + sourceId + " is active, unsubscribing from it");
si.sourceTracking.unsubscribe();
si.sourceTracking = null;
}
// source is disabled, remove it from list
Log.i(LOG_TAG, "Source " + sourceId + " is disabled ");
sources.remove(sourceId);
}
/**
* User can get value from any source by using source ID.
* #param sourceId
* #return
* #throws IllegalArgumentException
*/
public String getLastSourceValue(Integer sourceId) throws IllegalArgumentException{
if (!sources.containsKey(sourceId)){
throw new IllegalArgumentException("There is no source with id: " + sourceId);
}
String lastValue = sources.get(sourceId).lastData.data;
return lastValue;
}
public void setSourceUpdateCallback(Action1<Integer> sourceUpdateFeedback) {
this.sourceUpdateCallback = sourceUpdateFeedback;
}
}
And we can use it in UI like this:
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.EditText;
import android.widget.Toast;
import butterknife.BindView;
import butterknife.ButterKnife;
public class EdiTextTestActivity extends Activity {
#BindView(R.id.aet_et1)
public EditText et1;
#BindView(R.id.aet_et2)
public EditText et2;
#BindView(R.id.aet_et3)
public EditText et3;
private MultiSourceCombinator multiSourceCombinator;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_edit_text_test);
ButterKnife.bind(this);
multiSourceCombinator = new MultiSourceCombinator();
multiSourceCombinator.setSourceUpdateCallback(id -> {
Toast.makeText(EdiTextTestActivity.this, "New value from source: " + id + " : " +
multiSourceCombinator.getLastSourceValue(id), Toast.LENGTH_SHORT).show();
});
}
#Override
protected void onPause() {
// stop tracking all fields
multiSourceCombinator.stop();
super.onPause();
}
#Override
protected void onResume() {
super.onResume();
// Register fields
multiSourceCombinator.addSource(et1, R.id.aet_et1);
multiSourceCombinator.addSource(et2, R.id.aet_et2);
multiSourceCombinator.addSource(et3, R.id.aet_et3);
}
}

I have a solution for you without using lambda expressions (as I could not compile it with lambdas).
Use the same operator as you wanted:
public static <T, R> Observable<R> combineLatest(List<? extends Observable<? extends T>> sources, FuncN<? extends R> combineFunction)
Observable.combineLatest(observableList, new FuncN<Boolean>() {
#Override
public Boolean call(Object... objects) {
boolean isValid = true;
CharSequence input;
for (int i = 0; i < objects.length; i++) {
input = (CharSequence) objects[i];
switch (i) {
case 1:
//First text field value
break;
case 2:
//Second text field value
break;
default:
isValid = false;
}
}
return isValid;
}
})
The reason lambda expressions don't work is probably in second parameter of the function combineLatest(...):
public interface FuncN<R> extends Function {
R call(Object... args);
}
According to this post implementing Arbitrary Number of Arguments is hard to do and workarounds need to be created.
RxJava v2 is compatible with Java 8 and has a different implementation of combineLatest

I used R. Zagórski's answer as guide on how to do this with Kotlin
This is what worked for me in the end.
val ob1 = RxTextView.textChanges(field1).skip(1)
val ob2 = RxTextView.textChanges(field2).skip(1)
val ob3 = RxTextView.textChanges(field3).skip(1)
val observableList = arrayListOf<Observable<CharSequence>>()
observableList.add(ob1)
observableList.add(ob3)
val formValidator = Observable.combineLatest(observableList, {
var isValid = true
it.forEach {
val string = it.toString()
if (string.isEmpty()) {
isValid = false
}
}
return#combineLatest isValid
})
formValidator.subscribe { isValid ->
if (isValid) {
//do something
} else {
//do something
}
}

Related

Project Oxford Speech Recognition TextView edit

I have an EditText and a TextView, in which when I use speech recognition to talk, it is writing on the both.
The problem is when I press a button again the contents editText and textView get deleted.
Can somebody help me to continue from where I stopped?
For example, if I say "hey how are you" it writes it. On clicking start button lets
say I said "i'm ok". I want the second response to be "hey
how are you i'm ok " on the textview or edit text (Appended), and so on. However, it is overwriting the code.
Below is my code. Please help.
int m_waitSeconds = 0;
DataRecognitionClient dataClient = null;
MicrophoneRecognitionClient micClient = null;
FinalResponseStatus isReceivedResponse = FinalResponseStatus.NotReceived;
EditText _logText;
RadioGroup _radioGroup;
Button _buttonSelectMode;
Button _startButton;
EditText edit;
TextView txt;
String a;
public enum FinalResponseStatus { NotReceived, OK, Timeout }
/**
* Gets the primary subscription key
*/
public String getPrimaryKey() {
return this.getString(R.string.primaryKey);
}
/**
* Gets the LUIS application identifier.
* #return The LUIS application identifier.
*/
private String getLuisAppId() {
return this.getString(R.string.luisAppID);
}
/**
* Gets the LUIS subscription identifier.
* #return The LUIS subscription identifier.
*/
private String getLuisSubscriptionID() {
return this.getString(R.string.luisSubscriptionID);
}
/**
* Gets a value indicating whether or not to use the microphone.
* #return true if [use microphone]; otherwise, false.
*/
private Boolean getUseMicrophone() {
int id = this._radioGroup.getCheckedRadioButtonId();
return id == R.id.micIntentRadioButton ||
id == R.id.micDictationRadioButton ||
id == (R.id.micRadioButton - 1);
}
/**
* Gets a value indicating whether LUIS results are desired.
* #return true if LUIS results are to be returned otherwise, false.
*/
private Boolean getWantIntent() {
int id = this._radioGroup.getCheckedRadioButtonId();
return id == R.id.dataShortIntentRadioButton ||
id == R.id.micIntentRadioButton;
}
/**
* Gets the current speech recognition mode.
* #return The speech recognition mode.
*/
private SpeechRecognitionMode getMode() {
int id = this._radioGroup.getCheckedRadioButtonId();
if (id == R.id.micDictationRadioButton ||
id == R.id.dataLongRadioButton) {
return SpeechRecognitionMode.LongDictation;
}
return SpeechRecognitionMode.ShortPhrase;
}
/**
* Gets the default locale.
* #return The default locale.
*/
private String getDefaultLocale() {
return "en-us";
// ru-ru
}
/**
* Gets the short wave file path.
* #return The short wave file.
*/
private String getShortWaveFile() {
return "whatstheweatherlike.wav";
}
/**
* Gets the long wave file path.
* #return The long wave file.
*/
private String getLongWaveFile() {
return "batman.wav";
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
txt = (TextView) findViewById(R.id.txt1);
edit = (EditText) findViewById(R.id.edit1);
this._logText = (EditText) findViewById(R.id.editText1);
this._radioGroup = (RadioGroup)findViewById(R.id.groupMode);
this._buttonSelectMode = (Button)findViewById(R.id.buttonSelectMode);
this._startButton = (Button) findViewById(R.id.button1);
if (getString(R.string.primaryKey).startsWith("Please")) {
new AlertDialog.Builder(this)
.setTitle(getString(R.string.add_subscription_key_tip_title))
.setMessage(getString(R.string.add_subscription_key_tip))
.setCancelable(false)
.show();
}
// setup the buttons
final MainActivity This = this;
this._startButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View arg0) {
This.StartButton_Click(arg0);
}
});
this._buttonSelectMode.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View arg0) {
This.ShowMenu(This._radioGroup.getVisibility() == View.INVISIBLE);
}
});
this._radioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(RadioGroup rGroup, int checkedId) {
This.RadioButton_Click(rGroup, checkedId);
}
});
this.ShowMenu(true);
}
private void ShowMenu(boolean show) {
if (show) {
this._radioGroup.setVisibility(View.VISIBLE);
this._logText.setVisibility(View.INVISIBLE);
} else {
this._radioGroup.setVisibility(View.INVISIBLE);
this._logText.setText("");
this._logText.setVisibility(View.VISIBLE);
}
}
/**
* Handles the Click event of the _startButton control.
*/
private void StartButton_Click(View arg0) {
this._startButton.setEnabled(false);
this._radioGroup.setEnabled(false);
this.m_waitSeconds = this.getMode() == SpeechRecognitionMode.ShortPhrase ? 20 : 200;
this.ShowMenu(false);
this.LogRecognitionStart();
if (this.getUseMicrophone()) {
if (this.micClient == null) {
if (this.getWantIntent()) {
this.WriteLine("--- Start microphone dictation with Intent detection ----");
this.micClient =
SpeechRecognitionServiceFactory.createMicrophoneClientWithIntent(
this,
this.getDefaultLocale(),
this,
this.getPrimaryKey(),
this.getLuisAppId(),
this.getLuisSubscriptionID());
}
else
{
this.micClient = SpeechRecognitionServiceFactory.createMicrophoneClient(
this,
this.getMode(),
this.getDefaultLocale(),
this,
this.getPrimaryKey());
}
}
this.micClient.startMicAndRecognition();
}
else
{
if (null == this.dataClient) {
if (this.getWantIntent()) {
this.dataClient =
SpeechRecognitionServiceFactory.createDataClientWithIntent(
this,
this.getDefaultLocale(),
this,
this.getPrimaryKey(),
this.getLuisAppId(),
this.getLuisSubscriptionID());
}
else {
this.dataClient = SpeechRecognitionServiceFactory.createDataClient(
this,
this.getMode(),
this.getDefaultLocale(),
this,
this.getPrimaryKey());
}
}
this.SendAudioHelper((this.getMode() == SpeechRecognitionMode.ShortPhrase) ? this.getShortWaveFile() : this.getLongWaveFile());
}
}
/**
* Logs the recognition start.
*/
private void LogRecognitionStart() {
String recoSource;
if (this.getUseMicrophone()) {
recoSource = "microphone";
} else if (this.getMode() == SpeechRecognitionMode.ShortPhrase) {
recoSource = "short wav file";
} else {
recoSource = "long wav file";
}
this.WriteLine("\n--- Start speech recognition using " + recoSource + " with " + this.getMode() + " mode in " + this.getDefaultLocale() + " language ----\n\n");
}
private void SendAudioHelper(String filename) {
RecognitionTask doDataReco = new RecognitionTask(this.dataClient, this.getMode(), filename);
try
{
doDataReco.execute().get(m_waitSeconds, TimeUnit.SECONDS);
}
catch (Exception e)
{
doDataReco.cancel(true);
isReceivedResponse = FinalResponseStatus.Timeout;
}
}
public void onFinalResponseReceived(final RecognitionResult response) {
boolean isFinalDicationMessage = this.getMode() == SpeechRecognitionMode.LongDictation &&
(response.RecognitionStatus == RecognitionStatus.EndOfDictation ||
response.RecognitionStatus == RecognitionStatus.DictationEndSilenceTimeout);
if (null != this.micClient && this.getUseMicrophone() && ((this.getMode() == SpeechRecognitionMode.ShortPhrase) || isFinalDicationMessage)) {
// we got the final result, so it we can end the mic reco. No need to do this
// for dataReco, since we already called endAudio() on it as soon as we were done
// sending all the data.
this.micClient.endMicAndRecognition();
}
if (isFinalDicationMessage) {
this._startButton.setEnabled(true);
this.isReceivedResponse = FinalResponseStatus.OK;
}
if (!isFinalDicationMessage) {
this.WriteLine("********* Final n-BEST Results *********");
for (int i = 0; i < response.Results.length; i++) {
this.WriteLine("[" + i + "]" + " Confidence=" + response.Results[i].Confidence +
" Text=\"" + response.Results[i].DisplayText + "\"");
// edit.setText(response.Results[i].DisplayText);
}
this.WriteLine();
}
}
/**
* Called when a final response is received and its intent is parsed
*/
public void onIntentReceived(final String payload) {
this.WriteLine("--- Intent received by onIntentReceived() ---");
this.WriteLine(payload);
this.WriteLine();
}
public void onPartialResponseReceived(final String response) {
int a = edit.length();
edit.setText(response);
txt.setText(edit.getText().toString());
// this.WriteLine("--- Partial result received by onPartialResponseReceived() ---");
this.WriteLine(response);
this.WriteLine();
}
public void onError(final int errorCode, final String response) {
this._startButton.setEnabled(true);
this.WriteLine("--- Error received by onError() ---");
this.WriteLine("Error code: " + SpeechClientStatus.fromInt(errorCode) + " " + errorCode);
this.WriteLine("Error text: " + response);
this.WriteLine();
}
/**
* Called when the microphone status has changed.
* #param recording The current recording state
*/
public void onAudioEvent(boolean recording) {
this.WriteLine("--- Microphone status change received by onAudioEvent() ---");
this.WriteLine("********* Microphone status: " + recording + " *********");
if (recording) {
this.WriteLine("Please start speaking.");
}
WriteLine();
if (!recording) {
this.micClient.endMicAndRecognition();
this._startButton.setEnabled(true);
}
}
/**
* Writes the line.
*/
private void WriteLine() {
this.WriteLine("");
}
/**
* Writes the line.
* #param text The line to write.
*/
private void WriteLine(String text) {
this._logText.append(text + " ");
}
/**
* Handles the Click event of the RadioButton control.
* #param rGroup The radio grouping.
* #param checkedId The checkedId.
*/
private void RadioButton_Click(RadioGroup rGroup, int checkedId) {
// Reset everything
if (this.micClient != null) {
this.micClient.endMicAndRecognition();
try {
this.micClient.finalize();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
this.micClient = null;
}
if (this.dataClient != null) {
try {
this.dataClient.finalize();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
this.dataClient = null;
}
this.ShowMenu(false);
this._startButton.setEnabled(true);
}
/*
* Speech recognition with data (for example from a file or audio source).
* The data is broken up into buffers and each buffer is sent to the Speech Recognition Service.
* No modification is done to the buffers, so the user can apply their
* own VAD (Voice Activation Detection) or Silence Detection
*
* #param dataClient
* #param recoMode
* #param filename
*/
private class RecognitionTask extends AsyncTask<Void, Void, Void> {
DataRecognitionClient dataClient;
SpeechRecognitionMode recoMode;
String filename;
RecognitionTask(DataRecognitionClient dataClient, SpeechRecognitionMode recoMode, String filename) {
this.dataClient = dataClient;
this.recoMode = recoMode;
this.filename = filename;
}
#Override
protected Void doInBackground(Void... params) {
try {
// Note for wave files, we can just send data from the file right to the server.
// In the case you are not an audio file in wave format, and instead you have just
// raw data (for example audio coming over bluetooth), then before sending up any
// audio data, you must first send up an SpeechAudioFormat descriptor to describe
// the layout and format of your raw audio data via DataRecognitionClient's sendAudioFormat() method.
// String filename = recoMode == SpeechRecognitionMode.ShortPhrase ? "whatstheweatherlike.wav" : "batman.wav";
InputStream fileStream = getAssets().open(filename);
int bytesRead = 0;
byte[] buffer = new byte[1024];
do {
// Get Audio data to send into byte buffer.
bytesRead = fileStream.read(buffer);
if (bytesRead > -1) {
// Send of audio data to service.
dataClient.sendAudio(buffer, bytesRead);
}
} while (bytesRead > 0);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
finally {
dataClient.endAudio();
}
return null;
}
}
i edited in onPartialResponseReceived()

Cloud Console Datastore filters

I recently switched from JDO to Objectify in an effort to simplify some of my backend (I'm a beginner with App Engine and server side stuff in general).
I have an entity, AppVersion that used to look like this in Cloud Console:
When I switched to objectify, it no longer has the option to filter by minVersionRequired and looks like this:
Entity Code (Before)
import javax.persistence.Entity;
import javax.persistence.Id;
#Entity
public class AppVersion {
#Id
private String applicationName;
private int minVersionRequired;
public String getApplicationName() {
return applicationName;
}
public int getMinVersionRequired() {
return minVersionRequired;
}
public void setApplicationName(String applicationName) {
this.applicationName = applicationName;
}
public void setminVersionRequired(int minVersionRequired) {
this.minVersionRequired = minVersionRequired;
}
}
Entity Code (After)
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
#Entity
public class AppVersion {
#Id
private String applicationName;
private int minVersionRequired;
public String getApplicationName() {
return applicationName;
}
public int getMinVersionRequired() {
return minVersionRequired;
}
public void setApplicationName(String applicationName) {
this.applicationName = applicationName;
}
public void setminVersionRequired(int minVersionRequired) {
this.minVersionRequired = minVersionRequired;
}
}
Endpoint Code (Before) Note that this was autogenerated in Eclipse
import com.companionfree.zooperthemeviewer.EMF;
import com.google.api.server.spi.config.Api;
import com.google.api.server.spi.config.ApiMethod;
import com.google.api.server.spi.config.ApiNamespace;
import com.google.api.server.spi.response.CollectionResponse;
import com.google.appengine.api.datastore.Cursor;
import com.google.appengine.datanucleus.query.JPACursorHelper;
import java.util.List;
import javax.annotation.Nullable;
import javax.inject.Named;
import javax.persistence.EntityExistsException;
import javax.persistence.EntityNotFoundException;
import javax.persistence.EntityManager;
import javax.persistence.Query;
#Api(name = "appversionendpoint", namespace = #ApiNamespace(ownerDomain = "company.com", ownerName = "company.com", packagePath = "app"))
public class AppVersionEndpoint {
/**
* This method lists all the entities inserted in datastore.
* It uses HTTP GET method and paging support.
*
* #return A CollectionResponse class containing the list of all entities
* persisted and a cursor to the next page.
*/
#SuppressWarnings({ "unchecked", "unused" })
#ApiMethod(name = "listAppVersion")
public CollectionResponse<AppVersion> listAppVersion(
#Nullable #Named("cursor") String cursorString,
#Nullable #Named("limit") Integer limit) {
EntityManager mgr = null;
Cursor cursor = null;
List<AppVersion> execute = null;
try {
mgr = getEntityManager();
Query query = mgr
.createQuery("select from AppVersion as AppVersion");
if (cursorString != null && cursorString != "") {
cursor = Cursor.fromWebSafeString(cursorString);
query.setHint(JPACursorHelper.CURSOR_HINT, cursor);
}
if (limit != null) {
query.setFirstResult(0);
query.setMaxResults(limit);
}
execute = (List<AppVersion>) query.getResultList();
cursor = JPACursorHelper.getCursor(execute);
if (cursor != null)
cursorString = cursor.toWebSafeString();
// Tight loop for fetching all entities from datastore and accomodate
// for lazy fetch.
for (AppVersion obj : execute)
;
} finally {
mgr.close();
}
return CollectionResponse.<AppVersion> builder().setItems(execute)
.setNextPageToken(cursorString).build();
}
/**
* This method gets the entity having primary key id. It uses HTTP GET method.
*
* #param id the primary key of the java bean.
* #return The entity with primary key id.
*/
#ApiMethod(name = "getAppVersion")
public AppVersion getAppVersion(#Named("id") String id) {
EntityManager mgr = getEntityManager();
AppVersion appversion = null;
try {
appversion = mgr.find(AppVersion.class, id);
} finally {
mgr.close();
}
return appversion;
}
/**
* This inserts a new entity into App Engine datastore. If the entity already
* exists in the datastore, an exception is thrown.
* It uses HTTP POST method.
*
* #param appversion the entity to be inserted.
* #return The inserted entity.
*/
#ApiMethod(name = "insertAppVersion")
public AppVersion insertAppVersion(AppVersion appversion) {
EntityManager mgr = getEntityManager();
try {
if (containsAppVersion(appversion)) {
throw new EntityExistsException("Object already exists");
}
mgr.persist(appversion);
} finally {
mgr.close();
}
return appversion;
}
/**
* This method is used for updating an existing entity. If the entity does not
* exist in the datastore, an exception is thrown.
* It uses HTTP PUT method.
*
* #param appversion the entity to be updated.
* #return The updated entity.
*/
#ApiMethod(name = "updateAppVersion")
public AppVersion updateAppVersion(AppVersion appversion) {
EntityManager mgr = getEntityManager();
try {
if (!containsAppVersion(appversion)) {
throw new EntityNotFoundException("Object does not exist");
}
mgr.persist(appversion);
} finally {
mgr.close();
}
return appversion;
}
/**
* This method removes the entity with primary key id.
* It uses HTTP DELETE method.
*
* #param id the primary key of the entity to be deleted.
*/
#ApiMethod(name = "removeAppVersion")
public void removeAppVersion(#Named("id") String id) {
EntityManager mgr = getEntityManager();
try {
AppVersion appversion = mgr.find(AppVersion.class, id);
mgr.remove(appversion);
} finally {
mgr.close();
}
}
private boolean containsAppVersion(AppVersion appversion) {
EntityManager mgr = getEntityManager();
boolean contains = true;
try {
AppVersion item = mgr.find(AppVersion.class,
appversion.getApplicationName());
if (item == null) {
contains = false;
}
} finally {
mgr.close();
}
return contains;
}
private static EntityManager getEntityManager() {
return EMF.get().createEntityManager();
}
}
Endpoints Code (After) Note this was created by me in Android Studio
import com.google.api.server.spi.config.Api;
import com.google.api.server.spi.config.ApiMethod;
import com.google.api.server.spi.config.ApiNamespace;
import com.google.api.server.spi.response.CollectionResponse;
import java.util.List;
import javax.inject.Named;
import static com.company.backend.OfyService.ofy;
#Api(name = "appversionendpoint", version = "v1", namespace =
#ApiNamespace(ownerDomain = "backend.company.com",
ownerName = "backend.company.com", packagePath = ""))
public class AppVersionEndpoint {
#ApiMethod(name = "listAppVersion")
public CollectionResponse<AppVersion> listAppVersion() {
List<AppVersion> execute;
execute = ofy().load().type(AppVersion.class).list();
return CollectionResponse.<AppVersion> builder().setItems(execute).build();
}
/**
* This method gets the entity having primary key id. It uses HTTP GET method.
*
* #param id the primary key of the java bean.
* #return The entity with primary key id (null if DNE).
*/
#ApiMethod(name = "getAppVersion")
public AppVersion getAppVersion(#Named("id") String id) {
return ofy().load().type(AppVersion.class).id(id).now();
}
/**
* This inserts a new entity into App Engine datastore. If the entity already
* exists in the datastore, an exception is thrown.
* It uses HTTP POST method.
*
* #param appversion the entity to be inserted.
* #return The inserted entity.
*/
#ApiMethod(name = "insertAppVersion")
public AppVersion insertAppVersion(AppVersion appversion) {
AppVersion exist = getAppVersion(appversion.getApplicationName());
AppVersion result;
if (exist == null) {
ofy().save().entity(appversion).now();
result = getAppVersion(appversion.getApplicationName());
} else {
throw new IllegalArgumentException(appversion.getApplicationName() + " exists already");
}
return result;
}
}
I would much prefer to have it filterable like it was originally but I don't know why it is different. Can anyone fill me in?
There are two things going on here: firstly, by default, Objectify assumes you don't want to index your class's properties (this keeps your datastore indexes lean and mean). Secondly, I believe the new Datastore console's filter UI only shows properties which have indexes associated with them (as you can't filter on unindexed properties).
So, if you want to be able to query or sort by minVersionRequired, just add an #Index annotation to that field in your POJO and Objectify will use the setIndexedProperty() method in underlying Entity class within the low level Datastore API.
If you want to index all the properties in your class by default, you can put the #Index annotation on the class and then #Unindex any you specifically don't want indexed.

Android consuming RestService with/without cache

I am currently in the process of creating a high performance mobile application. Now i am looking at various design patterns for consuming rest services. One such pattern that stands out is the Google IO discussion here. How i have am looking at the code to develop this design. I will be using Spring Rest for doing the actual HTTP Rest and serialization to POJO with the Serialization Library. I came across this implementation here, and will be using it as a blue print for my application. Now a major question is here.
public interface HttpMethods {
public Object getForObject(Object ... params);
public Object putForObject(Object ... params);
}
public class LocationsHttpMethods implements HttpMethods{
private final Context mContext;
public LocationsHttpMethods(Context context)
{
mContext=context;
}
#Override
public Location[] getForObject(Object... params) {
return null;
}
#Override
public Object putForObject(Object... params) {
return null;
}
}
My Location is just a pojo class. Now the question that troubles me is that the second link that i have given just uses Boolean to return data. I will be returning an array of something.
package com.confiz.rest.services;
import java.util.ArrayList;
import java.util.HashMap;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import com.confiz.rest.providers.IProvider;
import com.confiz.rest.providers.LocationsProvider;
public class ProcessorService extends Service
{
private Integer lastStartId;
private final Context mContext = this;
/**
* The keys to be used for the required actions to start this service.
*/
public static class Extras
{
/**
* The provider which the called method is on.
*/
public static final String PROVIDER_EXTRA = "PROVIDER_EXTRA";
/**
* The method to call.
*/
public static final String METHOD_EXTRA = "METHOD_EXTRA";
/**
* The action to used for the result intent.
*/
public static final String RESULT_ACTION_EXTRA = "RESULT_ACTION_EXTRA";
/**
* The extra used in the result intent to return the result.
*/
public static final String RESULT_EXTRA = "RESULT_EXTRA";
}
private final HashMap<String, AsyncServiceTask> mTasks = new HashMap<String, AsyncServiceTask>();
/**
* Identifier for each supported provider.
* Cannot use 0 as Bundle.getInt(key) returns 0 when the key does not exist.
*/
public static class Providers
{
public static final int LOATIONS_PROVIDER = 1;
}
private IProvider GetProvider(int providerId)
{
switch(providerId)
{
case Providers.LOATIONS_PROVIDER:
return new LocationsProvider(this);
}
return null;
}
/**
* Builds a string identifier for this method call.
* The identifier will contain data about:
* What processor was the method called on
* What method was called
* What parameters were passed
* This should be enough data to identify a task to detect if a similar task is already running.
*/
private String getTaskIdentifier(Bundle extras)
{
String[] keys = extras.keySet().toArray(new String[0]);
java.util.Arrays.sort(keys);
StringBuilder identifier = new StringBuilder();
for (int keyIndex = 0; keyIndex < keys.length; keyIndex++)
{
String key = keys[keyIndex];
// The result action may be different for each call.
if (key.equals(Extras.RESULT_ACTION_EXTRA))
{
continue;
}
identifier.append("{");
identifier.append(key);
identifier.append(":");
identifier.append(extras.get(key).toString());
identifier.append("}");
}
return identifier.toString();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId)
{
// This must be synchronised so that service is not stopped while a new task is being added.
synchronized (mTasks)
{
// stopSelf will be called later and if a new task is being added we do not want to stop the service.
lastStartId = startId;
Bundle extras = intent.getExtras();
String taskIdentifier = getTaskIdentifier(extras);
Log.i("ProcessorService", "starting " + taskIdentifier);
// If a similar task is already running then lets use that task.
AsyncServiceTask task = mTasks.get(taskIdentifier);
if (task == null)
{
task = new AsyncServiceTask(taskIdentifier, extras);
mTasks.put(taskIdentifier, task);
// AsyncTasks are by default only run in serial (depending on the android version)
// see android documentation for AsyncTask.execute()
task.execute((Void[]) null);
}
// Add this Result Action to the task so that the calling activity can be notified when the task is complete.
String resultAction = extras.getString(Extras.RESULT_ACTION_EXTRA);
if (resultAction != "")
{
task.addResultAction(extras.getString(Extras.RESULT_ACTION_EXTRA));
}
}
return START_STICKY;
}
#Override
public IBinder onBind(Intent intent)
{
return null;
}
public class AsyncServiceTask extends AsyncTask<Void, Void, Object>
{
private final Bundle mExtras;
private final ArrayList<String> mResultActions = new ArrayList<String>();
private final String mTaskIdentifier;
/**
* Constructor for AsyncServiceTask
*
* #param taskIdentifier A string which describes the method being called.
* #param extras The Extras from the Intent which was used to start this method call.
*/
public AsyncServiceTask(String taskIdentifier, Bundle extras)
{
mTaskIdentifier = taskIdentifier;
mExtras = extras;
}
public void addResultAction(String resultAction)
{
if (!mResultActions.contains(resultAction))
{
mResultActions.add(resultAction);
}
}
#Override
protected Object doInBackground(Void... params)
{
Log.i("ProcessorService", "working " + mTaskIdentifier);
Object result = false;
final int providerId = mExtras.getInt(Extras.PROVIDER_EXTRA);
final int methodId = mExtras.getInt(Extras.METHOD_EXTRA);
if (providerId != 0 && methodId != 0)
{
final IProvider provider = GetProvider(providerId);
if (provider != null)
{
try
{
result = provider.RunTask(methodId, mExtras);
} catch (Exception e)
{
result = false;
}
}
}
return result;
}
#Override
protected void onPostExecute(Object result)
{
// This must be synchronised so that service is not stopped while a new task is being added.
synchronized (mTasks)
{
Log.i("ProcessorService", "finishing " + mTaskIdentifier);
// Notify the caller(s) that the method has finished executing
for (int i = 0; i < mResultActions.size(); i++)
{
Intent resultIntent = new Intent(mResultActions.get(i));
//What to do here
resultIntent.put(Extras.RESULT_EXTRA, true);
//What to do here ends.
resultIntent.putExtras(mExtras);
resultIntent.setPackage(mContext.getPackageName());
mContext.sendBroadcast(resultIntent);
}
// The task is complete so remove it from the running tasks list
mTasks.remove(mTaskIdentifier);
// If there are no other executing methods then stop the service
if (mTasks.size() < 1)
{
stopSelf(lastStartId);
}
}
}
}
}
Now if you browse to the code that contain the AsyncService, and puts the resultIntent.put(Extras.RESULT_EXTRA, true);
Now how should i pass the data back to the intent. I heard Serializable is bad, and Parceable is ugly code. What else can i use. Secondly, where do i add the SQL cache retrieve code. How can i add this code to the framework. Hope i make sense.

Consume WSDL to implement in Android ListView

I try to get the data from Web Service that I build with JAX-WS, WSDL and I want to implement in Android ListView.
This is my code in web service that I build on Netbeans.
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package in.figures.on.mobile;
import db.koneksi.dbKoneksi;
import java.sql.Statement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
import org.json.simple.JSONValue;
/**
*
* #author Setyadi
*/
#WebService()
public class AksesData {
/**
* Web service operation
*/
#WebMethod(operationName = "Lokasiku")
public String Lokasiku(
#WebParam(name = "lon") String lon,
#WebParam(name = "lat") String lat) {
//TODO write your implementation code here:
dbKoneksi con = new dbKoneksi();
Statement statement;
String sql = "SELECT desa "
+ "FROM dki "
+ "WHERE ST_Within(ST_SetSRID(ST_MakePoint("+lon+","+lat+"),0),geom);";
ResultSet hasil;
String desa = null;
try{
statement = con.getConnection().createStatement();
hasil = statement.executeQuery(sql);
hasil.next();
desa = hasil.getString(1);
}
catch(Exception e){
desa = "desa_thegagals";
}
finally{
}
if (con != null) {
return desa;
}
else {
return "lokasiku_thegagals";
}
}
/**
* Web service operation
*/
#WebMethod(operationName = "Kategori")
public String Kategori() {
//TODO write your implementation code here:
dbKoneksi con = new dbKoneksi();
Statement statement;
Properties properties;
List list = new ArrayList();
String sql = "SELECT kategori FROM kategori ";
ResultSet hasil;
String kategori = null;
try{
statement = con.getConnection().createStatement();
hasil = statement.executeQuery(sql);
while (hasil.next()) {
properties = new Properties();
properties.put("kategori", hasil.getString(1));
list.add(properties);
}
kategori = JSONValue.toJSONString(list);
}
catch(Exception e){
}
return kategori;
}
}
Does anybody want to help me, at least give me a tutorial about this.
Thanks in advance
Android doesn't provide any support for SOAP web services. They prefer we useRESTful web services with XML or JSON. however there are a few SOAP libraries out there. KSOAP is very popular, I personally have had issues with it. http://ksoap2.sourceforge.net/
Another suggestion is icesoap. it is a very simple library to use and understand, it worked like a charm for me.
http://code.google.com/p/icesoap/
For return data in 2D array
just get the data and convert it into String.
Then store it in Array
rs = s.executeQuery();
ResultSetMetaData rsmd = rs.getMetaData();
colCount = rsmd.getColumnCount();
String[][] result;
result = new String[size][rsmd.getColumnCount()];
int j=0;
while(rs.next())
{
for (int i = 1; i <= colCount; i++)
{
Object value=null;
if(!rsmd.getColumnTypeName(i).matches("varchar"))
{
if(rsmd.getColumnTypeName(i).matches("decimal"))
value=(Object)rs.getDouble(i);
else
value=(Object)rs.getObject(i);
}
else
{
value = (Object)rs.getString(i);
}
if(value!=null)
{
result[j][i-1]=value.toString();
}
else
{
result[j][i-1]="--";
}
}j++;
}
}
catch (SQLException | ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
and In android phone toy need to get that array and convert it into appropriate data type.
If you going to display it then you can display as it is. No need to convert into its original data type.

What happen if don't call stop() function in Android Google Analytics?

I've been looking through the code of the GoogleIO Android app and I notice the their did not call stop() function on the GoogleAnalytics' instance. What will happen if we don't call stop()?
This is the code:
package com.google.android.apps.iosched.util;
import com.google.android.apps.analytics.GoogleAnalyticsTracker;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Build;
import android.preference.PreferenceManager;
import android.util.Log;
/**
* Helper singleton class for the Google Analytics tracking library.
*/
public class AnalyticsUtils {
private static final String TAG = "AnalyticsUtils";
GoogleAnalyticsTracker mTracker;
private Context mApplicationContext;
/**
* The analytics tracking code for the app.
*/
// TODO: insert your Analytics UA code here.
private static final String UACODE = "INSERT_YOUR_ANALYTICS_UA_CODE_HERE";
private static final int VISITOR_SCOPE = 1;
private static final String FIRST_RUN_KEY = "firstRun";
private static final boolean ANALYTICS_ENABLED = true;
private static AnalyticsUtils sInstance;
/**
* Returns the global {#link AnalyticsUtils} singleton object, creating one if necessary.
*/
public static AnalyticsUtils getInstance(Context context) {
if (!ANALYTICS_ENABLED) {
return sEmptyAnalyticsUtils;
}
if (sInstance == null) {
if (context == null) {
return sEmptyAnalyticsUtils;
}
sInstance = new AnalyticsUtils(context);
}
return sInstance;
}
private AnalyticsUtils(Context context) {
if (context == null) {
// This should only occur for the empty Analytics utils object.
return;
}
mApplicationContext = context.getApplicationContext();
mTracker = GoogleAnalyticsTracker.getInstance();
// Unfortunately this needs to be synchronous.
mTracker.start(UACODE, 300, mApplicationContext);
Log.d(TAG, "Initializing Analytics");
// Since visitor CV's should only be declared the first time an app runs, check if
// it's run before. Add as necessary.
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mApplicationContext);
final boolean firstRun = prefs.getBoolean(FIRST_RUN_KEY, true);
if (firstRun) {
Log.d(TAG, "Analytics firstRun");
String apiLevel = Integer.toString(Build.VERSION.SDK_INT);
String model = Build.MODEL;
mTracker.setCustomVar(1, "apiLevel", apiLevel, VISITOR_SCOPE);
mTracker.setCustomVar(2, "model", model, VISITOR_SCOPE);
// Close out so we never run this block again, unless app is removed & =
// reinstalled.
prefs.edit().putBoolean(FIRST_RUN_KEY, false).commit();
}
}
public void trackEvent(final String category, final String action, final String label,
final int value) {
// We wrap the call in an AsyncTask since the Google Analytics library writes to disk
// on its calling thread.
new AsyncTask<Void, Void, Void>() {
#Override
protected Void doInBackground(Void... voids) {
try {
mTracker.trackEvent(category, action, label, value);
Log.d(TAG, "iosched Analytics trackEvent: "
+ category + " / " + action + " / " + label + " / " + value);
} catch (Exception e) {
// We don't want to crash if there's an Analytics library exception.
Log.w(TAG, "iosched Analytics trackEvent error: "
+ category + " / " + action + " / " + label + " / " + value, e);
}
return null;
}
}.execute();
}
public void trackPageView(final String path) {
// We wrap the call in an AsyncTask since the Google Analytics library writes to disk
// on its calling thread.
new AsyncTask<Void, Void, Void>() {
#Override
protected Void doInBackground(Void... voids) {
try {
mTracker.trackPageView(path);
Log.d(TAG, "iosched Analytics trackPageView: " + path);
} catch (Exception e) {
// We don't want to crash if there's an Analytics library exception.
Log.w(TAG, "iosched Analytics trackPageView error: " + path, e);
}
return null;
}
}.execute();
}
/**
* Empty instance for use when Analytics is disabled or there was no Context available.
*/
private static AnalyticsUtils sEmptyAnalyticsUtils = new AnalyticsUtils(null) {
#Override
public void trackEvent(String category, String action, String label, int value) {}
#Override
public void trackPageView(String path) {}
};
}
As you can see, they start the tracker with 5 minutes interval mTracker.start(UACODE, 300, mApplicationContext); but never call the mTracker.stop() method. Will there be any consequences? Does it mean the service will dispatch the data even when the app is closed or stopped?
EasyTracker - I found this blog entry in the google analytics blog. There they describe an EasyTracker class, that wraps the normal Tracker class and has some nice features.
Configuration via resource file (no coding needed)
Everything's done in a separate thread (not in the ui-thread as with the normal Tracker)
...
And this EasyTracker does not need to be stopped explicity either.
There shouldn't be happen much -- when you close or stop the app, and even if it still lives in the background and there's no activity anymore, no new data will be gathered (because no one invokes track() anymore. So I think it's just good manners to explicitly stop the tracker.

Categories

Resources