I am new to android development.
Currently I am implementing Firebase Realtime-Database with Rx android.
Here the Rx Android is used to listen to any changes happened in a particular child node inside the Realtime-Database, retrieve a List of Java class object in it and then return the List so that it can be used by another class.
Below are my code snippets.
1) Here is a class that do basic Database operation such as read, write, update and delete, right now I'm only showing the read operation.
public class FirebaseDatabaseLayer {
private DatabaseReference databaseReference = FirebaseDatabase
.getInstance().getReference();
private List<TodoComponentFirebase> todoComponentFirebases = new ArrayList<>();
private FirebaseUser user;
public Observable<List<TodoComponentFirebase>> readModelFirebase() {
return Observable.create(new Observable.OnSubscribe<List<TodoComponentFirebase>>() {
#Override
public void call(final Subscriber<? super List<TodoComponentFirebase>> subscriber) {
user = FirebaseAuth.getInstance().getCurrentUser();
databaseReference.child(user.getUid())
.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
for (DataSnapshot todoComponentSnapshot: dataSnapshot.getChildren()) {
TodoComponentFirebase todoComponentFirebase = todoComponentSnapshot.getValue(TodoComponentFirebase.class);
todoComponentFirebases.add(todoComponentFirebase);
}
subscriber.onNext(todoComponentFirebases);
subscriber.onCompleted();
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
for (DataSnapshot todoComponentSnapshot: dataSnapshot.getChildren()) {
TodoComponentFirebase todoComponentFirebase = todoComponentSnapshot.getValue(TodoComponentFirebase.class);
todoComponentFirebases.add(todoComponentFirebase);
}
subscriber.onNext(todoComponentFirebases);
subscriber.onCompleted();
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
});
}
2) Here is class which has a subscriber that listening to the Observable defined in the class in number 1) inside readModelFirebase method
public class Presenter {
private FirebaseDatabaseLayer firebaseDatabaseLayer;
private Subscription readSubscriber = null;
public void readFirebaseModel() {
readSubscriber = firebaseDatabaseLayer
.readModelFirebase()
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<List<TodoComponentFirebase>>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext (List<TodoComponentFirebase> todoComponentFirebases) {
mainView.getFirebaseData(todoComponentFirebases);
}
});
}
}
Here the 2) class pass the List of TodoComponentFirebase to the parameter of mainView.getFirebaseData method (I'm not showing concrete implementation of this method here). The idea here is that the mainView.getFirebaseData method will get a List of TodoComponentObject saved in Firebase Database everytime there's change happened in the respective child node.
MainView class is an AppCompatActivity class, the readFirebaseMethod() inside Presenter class is being called in onCreate method of the MainView class, this serve as subscription initialisation.
At the first time initialization of MainView class, the onNext inside readFirebaseModel() is executed, the data being retrieved seamlessly.
However after that there's no execution on that onNext method even though there's changes occur in Child Node which is defined in the 1) class.
I don't understand why the subscription is not working even though there's changes happen in the child node, it only worked the time it is being initialised. Is there anything missed in my Rx usage ?
Thanks
try remove the
subscriber.onCompleted();
I think when you call the onCompleted() subscriber will end up and not called again after any child event on firebase.
Related
I'm working on a JavaFX admin application that works with Firebase Realtime Database. I've created a ValueEventListener that acquires the data I need in onDataChange. With this data I want to call a call a method that does something with this data. The problem I ran into is that changing JavaFX elements from inside this onDataChange method isn't working at all.
I've tried narrowing the problem down by putting everything in one class and put all the functionality in the onDataChange method, but that wouldn't work either.
What's confusing me the most about this is that reading these elements is working just fine, while changing them doesn't result in anything. It does work when I execute this code from outside onDataChange.
Code in database helper class:
public void startDropsListener() {
dropsRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
OverviewController.getInstance().changeNodes();
}
#Override
public void onCancelled(DatabaseError databaseError) {
System.out.println("Reading from database failed: " + databaseError.getCode());
}
});
}
Method for changing nodes:
public void changeNodes() {
System.out.println(title.getText()); // This does return the node's text.
title.setText("New title"); // Does not update the node's text.
}
UPDATE:
Printing the title after changing seems to be working, so the value appears to update despite it not showing. Here's the instance related code in case that might be the issue:
private static OverviewController instance;
public OverviewController() {
instance = this;
}
public static OverviewController getInstance() {
if(instance == null){
instance = new OverviewController();
}
return instance;
}
Don't make the controller a singleton.
and do like this...
public class OverviewController implements Initializable{
#FXML
public Label label;
#Override
public void initialize(URL location, ResourceBundle bundle){
DatabaseReference dropsRef = FirbaseDatabase.DatabaseReference("Node");
dropsRef.addValueEventListener(valueEventListener);
}
ValueEventListener valueEventListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Plateform.runLater(()->label.setText(dataSnashot.getValue(SomeClass.class).getLabel());
}
#Override
public void onCancelled(DatabaseError databaseError) {
System.out.println("Reading from database failed: " + databaseError.getCode());
}
});
}
Is there scope for improvement in implementing Architecture Components
or in general considering:
Note: if you choose to use an AuthStateListener, make sure to unregister it before launching the FirebaseUI flow and re-register it after the flow returns. FirebaseUI performs auth operations internally which may trigger the listener before the flow is complete.
LiveData
public class FirebaseAuthLiveData extends LiveData<FirebaseUser> {
private FirebaseAuth firebaseAuth = FirebaseAuth.getInstance();
private FirebaseAuth.AuthStateListener authStateListener =
new FirebaseAuth.AuthStateListener() {
#Override
public void onAuthStateChanged(#NonNull FirebaseAuth firebaseAuth) {
FirebaseUser firebaseUser = firebaseAuth.getCurrentUser();
setValue(firebaseUser);
}
};
#Override
protected void onActive() {
super.onActive();
firebaseAuth.addAuthStateListener(authStateListener);
}
#Override
protected void onInactive() {
super.onInactive();
firebaseAuth.removeAuthStateListener(authStateListener);
}
}
ViewModel
public class FirebaseAuthViewModel extends ViewModel {
private final FirebaseAuthLiveData firebaseAuthLiveData = new
FirebaseAuthLiveData();
public LiveData<FirebaseUser> getFirebaseAuthLiveData() {
return firebaseAuthLiveData; }
}
}
MainActivity
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FirebaseAuthViewModel firebaseAuthViewModel =
ViewModelProviders.of(MainActivity.this).get(FirebaseAuthViewModel.class);
firebaseUserLiveData = firebaseAuthViewModel.getFirebaseAuthLiveData();
firebaseUserLiveData.observe(MainActivity.this, new Observer<FirebaseUser>() {
#Override
public void onChanged(#Nullable FirebaseUser firebaseUser) {
if (firebaseUser == null) {
final Intent intent = AuthUI.getInstance().createSignInIntentBuilder()
.setAvailableProviders(Collections.singletonList(
new AuthUI.IdpConfig.Builder(AuthUI.GOOGLE_PROVIDER).build())
).build();
startActivityForResult(intent, SIGN_IN);
} else {
updateUI(firebaseUser);
}
}
});
}
You're almost there. The single problem that you have is the use of the "FirebaseUser" object inside the activity, which breaks the MVVM architecture pattern, where is said that the activity should know nothing about its data source.
So the simplest and cleanest solution might be using a LiveData class:
class AuthLiveData(
private val auth: FirebaseAuth
): LiveData<Boolean>(), FirebaseAuth.AuthStateListener {
override fun onAuthStateChanged(auth: FirebaseAuth) {
value = auth.currentUser == null
}
override fun onActive() {
super.onActive()
auth.addAuthStateListener(this)
}
override fun onInactive() {
super.onInactive()
auth.removeAuthStateListener(this)
}
}
And a Repository class:
class MyRepository {
private val auth = FirebaseAuth.getInstance()
fun getFirebaseAuthState(): AuthLiveData {
return AuthLiveData(auth)
}
}
Now in the ViewModel class, we can simply:
class MyViewModel: ViewModel() {
val repository = MyRepository()
fun getAuthState(): LiveData<Boolean> {
return repository.getFirebaseAuthState()
}
}
In the end, in the activity we can observe the auth state changes like this:
viewModel.getAuthState().observe(this, { isUserSignedOut ->
if (isUserSignedOut) {
//Update the UI
}
})
This means that we'll always know when the user is signed in or not, without knowing which is the back-end.
I am using Retrofit 2.0 to retrieve data from my api and using a recyclerView to display it.
My main activity has a tab layout and one of those tabs has the recyclerView and the fragment class for that tab is being used to retrieve the data and update the layout.
In my main layout I have a fab which makes a post (all posts are being retrieved in fragment class) and this fab has it's function of making the post in main activity.
So how can I refresh the layout when the fab's function is over and the post is successfully saved in my database?
Basically
User clicks fab > Makes his post > Alert dialog closes > recyclerView should be refreshed with new data added.
My Fragment Class :
public class PostsRecentTab extends Fragment {
private static final String TAG = MainActivity.class.getSimpleName();
private RecyclerView feedView;
private ProgressDialog pDialog = MainActivity.pDialog;
LinearLayoutManager layoutManager;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View v = inflater.inflate(R.layout.tab_recent_posts, container, false);
pDialog.setCancelable(false);
layoutManager = new LinearLayoutManager(this.getContext());
feedView = (RecyclerView) v.findViewById(R.id.feedView);
requestData();
return v;
}
public void requestData() {
SocialHubAPI apiService = ApiClient.getClient().create(SocialHubAPI.class);
pDialog.setMessage("Refreshing...");
showDialog();
Call<StatusResponse> call = apiService.getStatuses();
call.enqueue(new Callback<StatusResponse>() {
#Override
public void onResponse(Call<StatusResponse> call, Response<StatusResponse> response) {
int statusCode = response.code();
List<Status> statuses = response.body().getStatuses();
Log.d(TAG, "Status Code: " + statusCode);
hideDialog();
updateView(statuses);
}
#Override
public void onFailure(Call<StatusResponse> call, Throwable t) {
Log.e(TAG, t.toString());
}
});
}
private void updateView(List<Status> statuses) {
StatusesAdapter adapter = new StatusesAdapter(statuses, R.layout.feed_item, getContext());
feedView.setLayoutManager(layoutManager);
feedView.setAdapter(adapter);
}
private void showDialog() {
if (!pDialog.isShowing())
pDialog.show();
}
private void hideDialog() {
if (pDialog.isShowing())
pDialog.dismiss();
}
}
My Fab On Click :
FloatingActionButton postStatus = (FloatingActionButton) findViewById(R.id.postStatus);
postStatus.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(final View view) {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle("Post Status");
// Set up the input
final EditText input = new EditText(MainActivity.this);
// Specify the type of input expected; this, for example, sets the input as a password, and will mask the text
input.setInputType(InputType.TYPE_CLASS_TEXT);
builder.setView(input);
// Set up the buttons
builder.setPositiveButton("Post", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
postText = input.getText().toString();
processPost(postText, sessionManager.getToken());
Snackbar.make(view, "Status posted!", Snackbar.LENGTH_LONG).show();
}
});
builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
builder.show();
}
});
Fab onClick calls this method :
protected void processPost(String postText, String token) {
SocialHubAPI apiService = ApiClient.getClient().create(SocialHubAPI.class);
pDialog.setMessage("Posting...");
showDialog();
final PostRequest postRequest = new PostRequest();
postRequest.setStatus(postText);
Call<PostResponse> call = apiService.postStatus(postRequest, token);
call.enqueue(new Callback<PostResponse>() {
#Override
public void onResponse(Call<PostResponse> call, Response<PostResponse> response) {
hideDialog();
Toast.makeText(getApplicationContext(), "Status Posted Successfully!", Toast.LENGTH_LONG).show();
}
#Override
public void onFailure(Call<PostResponse> call, Throwable t) {
Log.e(TAG, t.toString());
}
});
}
You should invalidate your list in updateView(List<Status> statuses) instead of setting the adapter again. Instantiate adapter only in onCreate().
This function should be like this:
adapter.addNewStatutes(statuses)
in Adapter class
public void addNewStatutes(List<Status> statuses)
{
this.statuses.addAll(statuses);
notifyDataSetChanged();
}
Also in onResponse use EventBus or Rx, because your view can be destroyed and this method can crash your app.
Added notifyDataSetChanged as per docs.
I have created a custom ObservableList implementation for a list of TreeItems. My custom implementation can listen to various notifications from inside my app (using OSGi EventAdmin), and update itself accordingly. I then expect its consumer (a TreeView widget) to be updated with the changes to the list. However, I can't see how to notify the consumer.
In the ObservableList subclass I am implementing addListener(ListChangeListener), which I would expect to get called when the object is added to the widget. However it is never called; I have no listeners thus no apparent way to notify anyone when the list changes. I must be missing something.
Here is a snippet from my TreeItem implementation, which returns an instance of my ObservableList in response to a getChildren call:
#Override
public ObservableList<TreeItem<DataObject>> getChildren() {
if (needChildren) {
needChildren = false;
children = new MyObservableList();
}
return children;
}
Here is an abridged version of my custom ObservableList implementation, which simply wraps an FXCollections.observableArrayList and adds an OSGi event handler. I listen to changes on the internal list so that I can pass those changes on to my listeners.
public class MyObservableList implements ObservableList<TreeItem<DataObject>>, EventHandler {
private List<ListChangeListener<? super TreeItem<DataObject>>> changeListeners = new ArrayList<>();
private List<InvalidationListener> invalidationListeners = new ArrayList<>();
private ObservableList<TreeItem<DataObject>> theList;
private int size;
public MyObservableList() {
theList = FXCollections.observableArrayList();
theList.addListener(new ListChangeListener<TreeItem<DataObject>>() {
#Override
public void onChanged(Change<? extends TreeItem<DataObject>> change) {
fireValueChangedEvent(change);
}
});
}
#Override
public int size() {
return theList.size();
}
#Override
public boolean isEmpty() {
return (size == 0);
}
#Override
public boolean contains(Object o) {
return theList.contains(o);
}
#Override
public Iterator iterator() {
return theList.iterator();
}
#Override
public boolean remove(Object o) {
return theList.remove(o);
}
#Override
public boolean addAll(Collection c) {
return theList.addAll(c);
}
#Override
public boolean addAll(int index, Collection c) {
return theList.addAll(index, c);
}
#Override
public void clear() {
theList.clear();
}
#Override
public TreeItem<DataObject> get(int index) {
return theList.get(index);
}
#Override
public int indexOf(Object o) {
return theList.indexOf(o);
}
#Override
public int lastIndexOf(Object o) {
return theList.lastIndexOf(o);
}
#Override
public ListIterator listIterator() {
return theList.listIterator();
}
#Override
public ListIterator listIterator(int index) {
return theList.listIterator(index);
}
#Override
public List<TreeItem<DataObject>> subList(int fromIndex, int toIndex) {
return theList.subList(fromIndex, toIndex);
}
#Override
public Object[] toArray(Object[] a) {
return theList.toArray(a);
}
#Override
public void addListener(ListChangeListener<? super TreeItem<DataObject>> listChangeListener) {
changeListeners.add(listChangeListener);
}
#Override
public void removeListener(ListChangeListener<? super TreeItem<DataObject>> listChangeListener) {
changeListeners.remove(listChangeListener);
}
#Override
public boolean addAll(TreeItem<DataObject>... treeItems) {
return theList.addAll(treeItems);
}
#Override
public boolean setAll(TreeItem<DataObject>... treeItems) {
return theList.setAll(treeItems);
}
#Override
public boolean setAll(Collection<? extends TreeItem<DataObject>> treeItems) {
return theList.setAll(treeItems);
}
#Override
public boolean removeAll(TreeItem<DataObject>... treeItems) {
return theList.removeAll(treeItems);
}
#Override
public boolean retainAll(TreeItem<DataObject>... treeItems) {
return theList.retainAll(treeItems);
}
#Override
public void remove(int i, int i2) {
theList.remove(i, i2);
}
#Override
public Object[] toArray() {
return theList.toArray();
}
#Override
public boolean add(TreeItem<DataObject> dataObjectTreeItem) {
return theList.add(dataObjectTreeItem);
}
#Override
public boolean containsAll(Collection<?> c) {
return theList.containsAll(c);
}
#Override
public boolean removeAll(Collection<?> c) {
return theList.removeAll(c);
}
#Override
public boolean retainAll(Collection<?> c) {
return theList.retainAll(c);
}
#Override
public TreeItem<DataObject> set(int index, TreeItem<DataObject> element) {
return theList.set(index, element);
}
#Override
public void add(int index, TreeItem<DataObject> element) {
theList.add(index, element);
}
#Override
public TreeItem<DataObject> remove(int index) {
return theList.remove(index);
}
#Override
public void addListener(InvalidationListener invalidationListener) {
invalidationListeners.add(invalidationListener);
}
#Override
public void removeListener(InvalidationListener invalidationListener) {
invalidationListeners.remove(invalidationListener);
}
private void fireValueChangedEvent(ListChangeListener.Change<? extends TreeItem<DataObject>> change) {
for (ListChangeListener<? super TreeItem<DataObject>> listener : changeListeners) {
listener.onChanged(change);
}
}
#Override
public void handleEvent(Event event) {
// Here I add or remove TreeItem<DataObject> instances to the list based on event.
//
// At this point, onChanged() gets called above in my listener, but my changeListeners list is empty. There is
// no one to pass the Change on to.
}
}
Thanks for any help.
I figured out what's going on here after looking through the JavaFX source.
It turns out that the listening on the ObservableList of children is all set up in the TreeItem itself, not the TreeView as I had somehow assumed. This means that any subclass of TreeView that overrides getChildren() must call super.getChildren() and add its children to the resulting list. This means that using a custom ObservableList implementation is not possible as TreeItem is hardcoded to use FXCollections.observableArrayList() to create the list.
I am taking a different approach to this now where I call super.getChildren(), add my children, and then instantiate another object that holds a reference to that list and does all of my app's event handling business, operating on the list as needed. So my getChildren() method looks something like this now.
private MyEventHandler eventHandler;
private ObservableList<TreeItem<DataObject>> children;
#Override
public ObservableList<TreeItem<DataObject>> getChildren() {
if (needChildren) {
needChildren = false;
children = super.getChildren();
eventHandler = new MyEventHandler(children); // handles app events
}
return children;
}
Currently, the project in question is set up using a ViewPager which has an adapter capable of loading 4 different fragments. The last 3 Fragments are dependent upon the first fragment giving them a value (the SQL foreign key comes from the a value from the first item in the ViewPager).
My question is: how do I retrieve a value from the first Fragment's EditText, perform an SQL Operation and pass a value once the user attempts to "swipe" to the next tab?
Note, that I am trying to do this on swipe, and not via button. So far I have attempted to override OnPageScrolled, which yielded a null pointer when referring to the first fragment. I am assuming that the first fragment has been destroyed when I attempt to call it's hasPopulatedClassName() function
For Reference, the offending activity. Note the comment under OnPageScrolled
public class ActivityClassEdit extends FragmentActivity implements ViewPager.
private ClassEditPagerAdapter aPager;
//Views
private ViewPager vPager;
//Boolean telling us if we are good to move on to Criteria, People, or Links
private boolean mClassInserted;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_class_edit);
if(getIntent().hasExtra(CM.BKEY_PASSED_SEMESTERKEY)){
//Set the views
vPager=(ViewPager) findViewById(R.id.ace_pager);
//Instantiate the pager and set it
aPager=new ClassEditPagerAdapter(this, getIntent().getExtras().getLong(CM.BKEY_PASSED_SEMESTERKEY, -1));
vPager.setAdapter(aPager);
vPager.setOnPageChangeListener(this);
} else {
//TODO handle not having a semester key
}
}
//=============PageScrollListener==============
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if(!mClassInserted) {
if (position == 1) {
//FragmentClassEdit fCE = (FragmentClassEdit) aPager.getRegisteredFragment(0);
FragmentClassEdit fCE = (FragmentClassEdit) aPager.getRegisteredFragment(0);
if (fCE.hasPopulatedClassName() == false) {
vPager.setCurrentItem(0);
Toast.makeText(this, getResources().getString(R.string.toast_typeInClassName), Toast.LENGTH_LONG).show();
} else {
//Save to shared Preferences, in case we have to delete on user cancellation
//null pointer here!!!
long mInsertedClassID= Long.getLong(fCE.insertClass());
getSharedPreferences(CM.SP, Activity.MODE_PRIVATE).edit().putLong(CM.SP_LASTINSERTEDCLASS, mInsertedClassID).apply();
aPager.setClassID(mInsertedClassID);
vPager.setCurrentItem(position);
}
}
} else {
vPager.setCurrentItem(position);
}
}
And here is the ViewPagerAdapter:
public class ClassEditPagerAdapter extends FragmentStatePagerAdapter{
private FragmentClassEdit fCE;
private Fragment fCrit;
private Fragment fLinks;
private Fragment fPeople;
private FragmentManager fragmentManager;
private String[] sTabText;
SparseArray<Fragment> registeredFragments = new SparseArray<Fragment>();
private Context ctx;
private long semID;
private long cID;
public ClassEditPagerAdapter(FragmentActivity context, long semesterID){
super(context.getSupportFragmentManager());
fragmentManager=context.getSupportFragmentManager();
ctx= context;
sTabText = ctx.getResources().getStringArray(R.array.cepa_titles);
cID=-1;
semID=semesterID;
}
public void setClassID(long classID){
cID=classID;
}
#Override
public Fragment getItem(int fragID) {
switch (fragID) {
//FragClassEdit
case 0:
fCE = FragmentClassEdit.newInstance(semID);
return fCE;
//FragTypeEdit
case 1:
fCrit = FragmentCriteriaEdit.newInstance(cID);
return fCrit;
//FragPeople
case 2:
fPeople = FragmentPeopleEdit.newInstance(cID);
return fPeople;
//FragLinks
case 3:
fLinks = FragmentLinkEdit.newInstance(cID);
return fLinks;
}
return null;
}
#Override
public Object instantiateItem(ViewGroup container, int position){
Fragment fragment = (Fragment) super.instantiateItem(container, position);
registeredFragments.put(position, fragment);
return fragment;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
registeredFragments.remove(position);
super.destroyItem(container, position, object);
}
public Fragment getRegisteredFragment(int position) {
return registeredFragments.get(position);
}
#Override
public int getCount() {
return 4;
}
#Override
public String getPageTitle(int position){
return sTabText[position];
}
}