I'm trying to implement ViewModel in a app which get some data from Firestore using the FirestorepagingAdapter, and displays it in a recyclerview. I'm already getting all data and displayig it, but still not using ViewModel, it's all on the MainActivity
I'm trying to move the code that creates Firestorepagingoptions to the the view model, and in my MainActivity, just create the adapter and set to the recyclerview. But the FirestorePagingOptions.Builder needs to set a LifecycleOwner and I don't know how to get it in my ViewModel, or maybe i should't be doing it on the ViewModel at all, I'm pretty lost yet with these ViewModel, so if anyone has any suggestion I appreciate. Thanks a lot.
Here's my original code on the MainActivity (not changed yet to get the data from the Viewmodel)
public class ListaEmpresasActivity extends AppCompatActivity {
private FirebaseFirestore firebaseDB;
private RecyclerView recyclerViewListaEmpresas;
private FirestorePagingAdapter adapter;
private boolean viewChanged;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_lista_empresas);
recyclerViewListaEmpresas = findViewById(R.id.activity_main_lista_empresas);
firebaseDB = FirebaseFirestore.getInstance();
FirestorePagingOptions<EmpresaLista> options = configuraPaginacaoInicial();
adapter = new ListaEmpresasAdapter(options);
configuraRecyclerView(adapter);
}
private FirestorePagingOptions<EmpresaLista> configuraPaginacaoInicial() {
Query query = firebaseDB.collection("Lista_Empresas").orderBy("id");
return configuraOpcoesPaginacao(query);
}
private FirestorePagingOptions<EmpresaLista> configuraOpcoesPaginacao(Query query) {
PagedList.Config config = new PagedList.Config.Builder()
.setEnablePlaceholders(false)
.setPrefetchDistance(10)
.setPageSize(20)
.build();
return new FirestorePagingOptions.Builder<EmpresaLista>()
.setLifecycleOwner(this)
.setQuery(query, config, EmpresaLista.class)
.build();
}
private void configuraRecyclerView(FirestorePagingAdapter adapter) {
recyclerViewListaEmpresas.setHasFixedSize(true);
recyclerViewListaEmpresas.setLayoutManager(new LinearLayoutManager(this));
recyclerViewListaEmpresas.setAdapter(this.adapter);
}
And the code on my ViewModel
public class ListaEmpresasViewModel extends ViewModel {
private MutableLiveData<FirestorePagingOptions<EmpresaLista>> options;
private FirebaseFirestore firebaseDB;
public LiveData<FirestorePagingOptions<EmpresaLista>> getOptions() {
firebaseDB = FirebaseFirestore.getInstance();
options = configuraPaginacaoInicial();
return options;
}
private MutableLiveData<FirestorePagingOptions<EmpresaLista>> configuraPaginacaoInicial() {
Query query = firebaseDB.collection("Lista_Empresas").orderBy("id");
return configuraOpcoesPaginacao(query);
}
private MutableLiveData<FirestorePagingOptions<EmpresaLista>> configuraOpcoesPaginacao(Query query) {
PagedList.Config config = new PagedList.Config.Builder()
.setEnablePlaceholders(false)
.setPrefetchDistance(10)
.setPageSize(20)
.build();
return new FirestorePagingOptions.Builder<EmpresaLista>()
.setLifecycleOwner()
.setQuery(query, config, EmpresaLista.class)
.build();
}
}
Instead of setting the lifecycle owner in the options start and stop listening manually.
Start/stop listening
The FirestorePagingAdapter listens for scrolling events and loads additional pages from the database only when needed.
To begin populating data, call the startListening() method. You may want to call this in your onStart() method. Make sure you have finished any authentication necessary to read the data before calling startListening() or your query will fail.
#Override
protected void onStart() {
super.onStart();
adapter.startListening();
}
Similarly, the stopListening() call freezes the data in the RecyclerView and prevents any future loading of data pages.
Call this method when the containing Activity or Fragment stops:
#Override
protected void onStop() {
super.onStop();
adapter.stopListening();
}
Related
The app closes whenever I navigate to ProfileActivity.
FATAL EXCEPTION: main Process: hfad.com.hallofmemesprototype, PID:
19092 java.lang.RuntimeException: Unable to start activity
ComponentInfo{hfad.com.hallofmemesprototype/hfad.com.hallofmemesprototype.Profile.ProfileActivity}:
java.lang.IllegalArgumentException: view must not be null
Here's the "ProfileActivity" code.
public class ProfileActivity extends AppCompatActivity {
private static final int ACTIVITY_NUM = 3;
private Context mContext = ProfileActivity.this;
private ProgressBar mProgressBar;
private ImageView profilePhoto;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_profile);
setupBottomNavigationView();
setupToolbar();
setupActivityWidgets();
setProfileImage();
}
private void setProfileImage(){
String imgURL = "www.androidzone.org/wp-content/uploads/2013/02/android-musical2.jpg";
UniversalImageLoader.setImage( imgURL, profilePhoto, mProgressBar, "https://");
}
private void setupActivityWidgets(){
mProgressBar = findViewById(R.id.profileProgressBar);
mProgressBar.setVisibility(View.GONE);
profilePhoto = findViewById(R.id.profile_photo);
}
private void setupToolbar() {
Toolbar toolbar = findViewById(R.id.profileToolBar);
setSupportActionBar(toolbar);
ImageView profileMenu = findViewById(R.id.profileMenu);
profileMenu.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent intent = new Intent(mContext, AccountSettingsActivity.class);
startActivity(intent);
}
});
}
/**
* BottomNavigationView setup
*/
private void setupBottomNavigationView() {
BottomNavigationViewEx bottomNavigationViewEx = findViewById(R.id.bottomNavViewBar);
BottomNavigationViewHelper.bottomNavigationView(bottomNavigationViewEx);
BottomNavigationViewHelper.enableNavigation(mContext, bottomNavigationViewEx);
Menu menu = bottomNavigationViewEx.getMenu();
MenuItem menuItem = menu.getItem(ACTIVITY_NUM);
menuItem.setChecked(true);
}
}
You must check that all the views that you are getting using findViewById() really exist into the activity_profile.xml layout.
One or more views doesn´t really exist, and you have a null value gettin the reference.
findViewById(R.id.profileProgressBar);
findViewById(R.id.profile_photo);
findViewById(R.id.profileToolBar);
findViewById(R.id.profileMenu);
findViewById(R.id.bottomNavViewBar);
initially you are not getting any error tryin to find the references of this views because they are really exist but in another layout but not in activity_profile.xml that you are loading via setContentView() in your Activity.
This type of exception rise at the time of the wrong variable declaration and
initialization.
Try this
replace your weighs declare and initialize val with var.
If you have to use java
final TextView helloTextView = (TextView)findViewById(R.id.text_view_id);
For Kotlin
val helloTextView = findViewById(R.id.text_view_id) as TextView
you can refer this link for more details
I have a game on LibGDX. According to this
http://www.norakomi.com/tutorial_admob_part2_banner_ads1.php
instruction I created necessery methods in AndroidLauncher.java file. And in the core file, generated by AndroidLauncher.java, I have created the controller and also interface java file
( http://www.norakomi.com/tutorial_admob_part2_banner_ads2.php ).
The problem is that my game has several classes which extend one another and the corresponding condition, which I want to use for displaying adMob, is not that one to which method "initialize" gives "this" from AndroidLauncher.java file. But to download and to give request for adMob is possible only from AndroidLauncher.java, because another classes are in its own game view.
How to solve this?
This is the basic code from AndroidLauncher.java
public class AndroidLauncher extends AndroidApplication implements AdsController {
private static final String BANNER_AD_UNIT_ID = "ca-app-pub-3940256099942544/6300978111";
private static final String INTERSTITIAL_AD_UNIT_ID = "ca-app-pub-3940256099942544/1033173712";
AdView bannerAd;
InterstitialAd interstitialAd;
#Override
protected void onCreate (Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AndroidApplicationConfiguration config = new AndroidApplicationConfiguration();
// Create a gameView and a bannerAd AdView
View gameView = initializeForView(new Stork2016(this), config);
setupBanner();
setupInterstitial();
// Define the layout
RelativeLayout layout = new RelativeLayout(this);
layout.addView(gameView, ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
layout.addView(bannerAd, params);
setContentView(layout);
config.useCompass = false;
config.useAccelerometer = false;
public void setupBanner() {
bannerAd = new AdView(this);
//bannerAd.setVisibility(View.VISIBLE);
//bannerAd.setBackgroundColor(0xff000000); // black
bannerAd.setAdUnitId(BANNER_AD_UNIT_ID);
bannerAd.setAdSize(AdSize.SMART_BANNER);
}
public void setupInterstitial() {
interstitialAd = new InterstitialAd(this);
interstitialAd.setAdUnitId(INTERSTITIAL_AD_UNIT_ID);
AdRequest.Builder builder = new AdRequest.Builder();
AdRequest ad = builder.build();
interstitialAd.loadAd(ad);
#Override
public void showInterstitialAd(final Runnable then) {
runOnUiThread(new Runnable() {
#Override
public void run() {
if (then != null) {
interstitialAd.setAdListener(new AdListener() {
#Override
public void onAdClosed() {
Gdx.app.postRunnable(then);
AdRequest.Builder builder = new AdRequest.Builder();
AdRequest ad = builder.build();
interstitialAd.loadAd(ad);
}
});
}
interstitialAd.show();
}
});
}
#Override
public boolean isWifiConnected() {
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo ni = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
return (ni != null && ni.isConnected());
}
#Override
public void showBannerAd() {
runOnUiThread(new Runnable() {
#Override
public void run() {
bannerAd.setVisibility(View.VISIBLE);
AdRequest.Builder builder = new AdRequest.Builder();
AdRequest ad = builder.build();
bannerAd.loadAd(ad);
}
});
}
#Override
public void hideBannerAd() {
runOnUiThread(new Runnable() {
#Override
public void run() {
bannerAd.setVisibility(View.INVISIBLE);
}
});
}
}
And then we have file Stork2016.java in which we create AdsController to be able to use methods for adds in AndroidLauncher.java.
private AdsController adsController;
public Stork2016(AdsController adsController){
this.adsController = adsController;
}
#Override
public void create () {
adsController.showBannerAd();
batch = new SpriteBatch();
gsm = new GameStateManager();
music = Gdx.audio.newMusic(Gdx.files.internal("music.mp3"));
music.setLooping(true);
music.setVolume(0.5f);
music.play();
Gdx.gl.glClearColor(1, 0, 0, 1);
gsm.push(new MenuState(gsm));
}
And also we have interface java file AdsController.java
public interface AdsController {
public void showBannerAd();
public void hideBannerAd();
public void showInterstitialAd (Runnable then);
public boolean isWifiConnected();
}
So, as we can see in Stork2016 we have "gsm.push(new MenuState(gsm));" and in MenuState.java I have "gsm.set(new PlayState(gsm));". In PlayState.java there is the part of code:
#Override
public void update(float dt) {
handleInput();
updateGround();
....
if (tube.collides(bird.getBounds()))
gsm.set(new GameOver(gsm));
...
}
}
camera.update();
}
The condition "if" frome the above code I want to use to show interstitial adMob. But it is impossibe, because the contoller which takes methods from AndroidLauncher.java can be created only in Stork2016.java. And also in AndroidLauncher.java there is
View gameView = initializeForView(new Stork2016(this), config);
wich transfers "this" to Stork2016, where is the controller.
In my AndroidLauncher activity I start the game and initialize the Insterstitial ad. Then I initialize my interface which I call from inside the game, to trigger show/hide of the interstitial ad.
For example I have method showInterstitialAd() in my interface listener, then my implementation on Android would be:
#Override
public void showCoverAd() {
runOnUiThread(new Runnable() {
#Override
public void run() {
if (interstitialAd.isLoaded()) {
interstitialAd.show();
}
}
});
}
And on iOS-MOE:
#Override
public void showCoverAd() {
if (gadInterstitial.isReady()) {
gadInterstitial.presentFromRootViewController(uiViewController);
}
}
So you need the make sure that the interface listener knows about the interstitial ad, for example AndroidLauncher implements MyGameEventListener
In my case interface AdsController.java is implemented in AndroidLauncher.java:
public class AndroidLauncher extends AndroidApplication implements AdsController { ...
And then by this part of code:
View gameView = initializeForView(new Stork2016(this), config);
we send "this" to new class Strork2016.java.
And in the class Stork2016.java I create constructor:
private AdsController adsController;
public Stork2016(AdsController adsController){
this.adsController = adsController;
}
which lets us use methods from interface AdsController.java.
But only in this class Stork2016. If I want to use it in another class:
gsm.push(new MenuState(gsm));
this is impossible and this is the problem.
OK guys, I have solved the problem.
I had to create two consturctors in both classes: the main core class which is initialyzed from AndroidLauncher and in the class GameStateManager. Because the class, where I want admob intersitital to be called, is created by method gsm.push which is described in class GameStateManager. Actually, in GameStateManager there have already been constuructor, so I hade only to add necessary code to this constructor.
I've read through a lot of questions on how to update a ListView. They all pretty much say adapter.notifyDataSetChanged() (if in a seperate thread with runOnUiThread).
My problem starts a bit earlier: How do I even get the Adapter I need or for that matter an Activity to call runOnUiThread?
Here is a very simplified version of my current code:
I have a class for the Data, which can be updated from anywhere at any time. The update method needs to start a new Thread, which is why I can't simply wait for a return value.
class Data {
private static double[] data = new double[7]
public static void update(final Context context) {
new Thread(new Runnable() {
#Override
public void run() {
//do lots of complicated stuff
}
}).start();
}
public double[] getData {
return data;
}
I have an Activity (not the main Activity) that, among other stuff, contains the ListView
public class ListViewActivity extends ActionBarActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ListView);
ListView listView = (ListView) findViewById(R.id.MyListView);
listView.setAdapter(new CustomListViewAdapter(this));
}
I then use a custom Adapter to set up the ListView how I want it.
class CustomListViewAdapter extends BaseAdapter {
private final LayoutInflater layoutInflater;
public CustomListViewAdapter(Context context) {
layoutInflater = LayoutInflater.from(context);
}
#Override
public View getView(int position, View view, ViewGroup parent) {
ViewHolder viewHolder = new ViewHolder();
if (view == null) {
view = layoutInflater.inflate(R.layout.listView_row_layout, parent, false);
viewHolder.value = (TextView) view.findViewById(R.id.listViewDataTextField);
view.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) view.getTag();
}
double data = Data.getData()[position];
viewHolder.value.setText(data+"");
return view;
}
private class ViewHolder {
TextView value;
}
Now I want to update the ListView with as soon as the Thread in the update() method has finished getting the new data. Getting the data can take several seconds, so it's impossible to predict, what activity will be active by that time. If my ListViewActivity is active, I want the ListView to change to the new data immediately.
So how do I update that ListView, from a different class, which is not an Activity, inside another thread? I don't suppose putting the adapter in a static field is a good idea, since that ListViewActivity is probably created and destroyed all the time? But what other options do I have?
Define a callback interface. Make your update method take an instance of this callback as an argument and call back to it when updating has finished.
public interface UpdateCallback {
public void onUpdateFinished();
}
public void update(final Context context, final UpdateCallback callback) {
new Thread(new Runnable() {
#Override
public void run() {
// do lots of complicated stuff
callback.onUpdateFinished();
}
}).start();
}
Then in your Activity you can notify that the adapter data has changed:
Data.update(this, new UpdateCallback() {
public void onUpdateFinished() {
adapter.notifyDatasetChanged();
}
});
extendIt looks like your major problem can be formulated shortly as "How do I even get the Adapter", so I'll try to address that. Let us try to use an old good Java singleton to solve the problem. The idea is to create a global config and pre-create adapters for all views that you need to update:
public class GlobalConfig {
private static GlobalConfig config = null;
private CustomBaseAdapterOne adapter1 = null;
private CustomBaseAdapterTwo adapter2 = null;
public GlobalConfig getAdapterOne() {return adapter1;}
public GlobalConfig getAdapterTwo() {return adapter2;}
public static synchronized GlobalConfig getInstance() {
if (config == null) {
config = new GlobalConfig();
config.adapter1 = new CustomBaseAdapterOne();
config.adapter2 = new CustomBaseAdapterTwo();
}
return config;
}
}
Your ListViewActivity will need to extend DataSetObserver class and register an observer when it starts. You CustomListAdapter would need to register the observer when it starts and it will also need to be able to get initialized with init params (Context in your case) through a function (e.g. setContext), not through a constructor, so in your ListViewActivity you'll need to do something like below:
GlobalConfig cfg = GlobalConfig.getInstance();
CustomerAdapterOne ad = cfg.getAdapterOne();
ad.setContext(this);
ad.registerDataSetObserver(this);
From a separate thread that needs to update the ListView, you'll run:
GlobalConfig cfg = GlobalConfig.getInstance();
try {
cfg.getAdapterOne().notifyDataSetChanged();
}
catch{... }
I think, it's a good idea to have try/catch here because I don't know what will happen if ListView has already died at the time when you need to notify.
To summarize: you'll need to pre-create one adapter for each view that needs to be updated and make it globally available for everyone who needs it.
following are my codes in main activity
public class MainActivity extends ActionBarActivity{
private MediaRouteButton mMediaRouteButton;
private MediaRouteSelector mMediaRouteSelector;
private MediaRouter mMediaRouter;
private CastDevice mSelectedDevice;
private MyMediaRouterCallback mMediaRouterCallback;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(this.checkGooglePlaySevices(this))
Log.v("cc5zhenhua","googleplayservice okay");
else
{
Log.v("cc5zhenhua","googleplayservice not ok");
//GooglePlayServicesUtil.getErrorDialog(0, this, 0).show();
}
//initialize media cast objects
mMediaRouter=MediaRouter.getInstance(getApplicationContext());
mMediaRouteSelector=new MediaRouteSelector.Builder()
.addControlCategory(CastMediaControlIntent.CATEGORY_CAST).build();
mMediaRouterCallback= new MyMediaRouterCallback();
mMediaRouter.addCallback(mMediaRouteSelector, mMediaRouterCallback);
}
public void onStart() {
super.onStart();
mMediaRouter.addCallback(mMediaRouteSelector, mMediaRouterCallback
);
MediaRouter.RouteInfo route = mMediaRouter.updateSelectedRoute(mMediaRouteSelector);
// do something with the route...
}
#Override
protected void onResume()
{
super.onResume();
mMediaRouter.addCallback(mMediaRouteSelector, mMediaRouterCallback);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
super.onCreateOptionsMenu(menu);
//mMediaRouteButton.setRouteSelector(mMediaRouteSelector);
return true;
}
#Override
public boolean onPrepareOptionsMenu(Menu menu){
getMenuInflater().inflate(R.menu.main, menu);
MenuItem mediaRouteItem = menu.findItem( R.id.action_mediaroute01 );
MediaRouteActionProvider mediaRouteActionProvider =
(MediaRouteActionProvider)MenuItemCompat.getActionProvider(
mediaRouteItem);
mediaRouteActionProvider.setRouteSelector(mMediaRouteSelector);
mMediaRouteButton = (MediaRouteButton) mediaRouteItem.getActionView();
return true;}
public boolean checkGooglePlaySevices(final Activity activity) {
final int googlePlayServicesCheck = GooglePlayServicesUtil.isGooglePlayServicesAvailable(
activity);
switch (googlePlayServicesCheck) {
case ConnectionResult.SUCCESS:
return true;
default:
Log.v("cc5zhenhua","test"); }
return false;
}
private class MyMediaRouterCallback extends MediaRouter.Callback
{
#Override
public void onRouteSelected(MediaRouter router, RouteInfo info) {
mSelectedDevice = CastDevice.getFromBundle(info.getExtras());
String routeId = info.getId();
Log.v("cc5zhenhua", "MainActivity.onRouteSelected");
}
#Override
public void onRouteUnselected(MediaRouter router, RouteInfo info) {
//teardown();
mSelectedDevice = null;
}
}
}
There's no build error. However when I run the main activity, the media route button can not be clicked at all. Please advise any where I missed? Thank you!
My chromecast is whitelisted registed with an APPID before the new SDK published.
I can't use that appID for the control category either, it throws not valida appID exception.
My cast device is also available for chromecast extension in my computer.
You need to start the scan by adding callbacks:
mMediaRouter.addCallback(mMediaRouteSelector, mMediaRouterCallback,
MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
If you are already doing that and forgot to mention that in your post, then you need to register your app and device on the Developer Console. Your issue is, then, most likely due to the whitelisting of your device; try connecting to your device from a chrome browser at http://<chromecast-ip>:9222, if you can't, then your device is not whitelisted; follow the steps in this post to trouble shoot that
Finally get the issue point. Just because that my last app with old googlecast sdk works on the AVD, so I focused on my codes and new SDK setting.However, when I deploy the app on real phone ,the media route can be found. Thanks to Ali for his kindness and helping.
I have a javafx design in the file javafx.fxml where the root element has the following attribute
fx:controller="de.roth.jsona.javafx.ViewManagerFX"
This controller class has a singleton machanism and is binded with some ui-elements.
public class ViewManagerFX {
private static ViewManagerFX instance = new ViewManagerFX();
#FXML
private Slider volumeSlider;
#FXML
private Label volumeLabel;
public IntegerProperty volumeValue = new SimpleIntegerProperty();
#FXML
private TabPane musicTabs;
public List<StringProperty> tabNames = new ArrayList<StringProperty>();
public static ViewManagerFX getInstance() {
return (instance);
}
public void initialize() {
// Volume
volumeSlider.valueProperty().bindBidirectional(volumeValue);
volumeLabel.textProperty().bindBidirectional(volumeValue, new Format() {
#Override
public StringBuffer format(Object obj, StringBuffer toAppendTo,
FieldPosition pos) {
toAppendTo.append(obj);
toAppendTo.append("%");
return toAppendTo;
}
#Override
public Object parseObject(String source, ParsePosition pos) {
return null; // no need to be implemented
}
});
volumeValue.set(Config.getInstance().VOLUME);
}
public void addMusicFolderTab(final String t, final ArrayList<MusicListItem> items) {
Platform.runLater(new Runnable() {
#Override
public void run() {
Tab m = new Tab("Test Tab");
musicTabs.getTabs().add(0, m);
}
});
}
}
The method addMusicFolderTab is called from a thread that is used to scan files and directories.
In the initialize method I can access the ui-elements but in the method addMusicFolderTab, that is called from the filescanner-thread, the variable musicTabs is null. Here is the exception:
java.lang.NullPointerException
at de.roth.jsona.javafx.ViewManagerFX$3.run(ViewManagerFX.java:110)
I have no clue, why I can't access the TabPane from outside the initialize method.
Aside from the many questionable patterns used here, the problem is that your ViewManagerFX singleton (besides not being a singleton) never has its instance set.
When using FXML, the Controller is created and loaded dynamically by Reflection from the FXMLoader.
What happens is that by calling ViewManagerFX.getInstance(), you access the a different controller than the one created by the FXMLoader. The instance you access is the one created here:
private static ViewManagerFX instance = new ViewManagerFX();
The quickest way to solve the issue is to set the instance in the initialize() since it's called by the FXMLoader on the instance created by the FXMLoader.
public void initialize() {
instance = this;
// Volume
...
}