I'm trying to adapt Mortar&Flow in my app and faced with an issue, that I can't make PageAdapter works with Screens, instead of Fragments.
Anyone managed to get it right?
I didn't succeed but, probably someone can guide me from this point:
The initial Dagger Registration:
#Module(
injects = {
MainActivity.class,
},
library = true,
complete = false
)
public class DaggerConfig {
#SuppressWarnings("unused")
#Provides #Singleton Gson provideGson() {
return new GsonBuilder().create();
}
}
MainScreen, whose View is hosting ViewPager:
#Layout(R.layout.screen_main) #WithModule(MainScreen.Module.class)
public class MainScreen extends Path {
#dagger.Module(injects = MainView.class, addsTo = DaggerConfig.class)
public static class Module {}
#Singleton
public static class Presenter extends ViewPresenter<MainView> {
#Inject
public Presenter() {}
}
}
MainView:
...........
#Inject
MainScreen.Presenter presenter;
...........
#Override protected void onFinishInflate() {
super.onFinishInflate();
ButterKnife.inject(this);
final Path[] screens = {
new SubScreen("1"),
new SubScreen("2"),
new SubScreen("3"),
};
CustomPagerAdapter customPagerAdapter = new CustomPagerAdapter(getContext(), screens );
customPagerAdapter .setAdapter(firstRunPagerAdapter);
}
.....
Now, the main part, SubScreen (3 similar screens, that differs only by the parameters we are passing into them => they should adjust views according these parameters)
#Layout(R.layout.screen_subscreen) #WithModule(SubScreen.Module.class)
public class SubScreen extends Path {
private final String title;
public SubScreen(String titleParam) {
title = titleParam;
}
#dagger.Module(injects = SubView.class, addsTo = DaggerConfig.class)
public class Module {
#Provides
SubViewMetadata provideSubViewMetadata() {
return new SubViewMetadata(backgroundColor, title);
}
}
#Singleton
public static class Presenter extends ViewPresenter<SubView> {
private String title;
#Inject
public Presenter(String title) {
this.title= title;
}
#Override
protected void onLoad(Bundle savedInstanceState) {
super.onLoad(savedInstanceState);
if (!hasView()) {
return;
}
getView().setTitle(subViewMetadata.title);
}
}
}
and it's view
public class SubView extends FrameLayout {
#InjectView(R.id.subViewTitleTextView)
TextView subViewTitleTextView;
#Inject
SubScreen.Presenter presenter;
public SubView(Context context, AttributeSet attrs) {
super(context, attrs);
ObjectGraphService.inject(context, this);
}
public void setTitle(String title) {
subViewTitleTextView.setText(title);
}
#Override protected void onAttachedToWindow() {....}
#Override protected void onDetachedFromWindow() {....}
......
}
Custom Pager adapter:
public class CustomPagerAdapter extends PagerAdapter {
private final Context context;
private final Path[] screens;
public CustomPagerAdapter(Context context, Path[] screens) {
this.context = context;
this.screens = screens;
}
#Override
public int getCount() {
return (screens == null)? 0 : screens.length;
}
#Override
public boolean isViewFromObject(View view, Object o) {
return view.equals(o);
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Path screen = screens[position];
MortarScope originalScope = MortarScope.getScope(context);
MortarScope newChildScope = originalScope.buildChild().build("tutorialpage" + position);
Context childContext = newChildScope.createContext(context);
View newChild = Layouts.createView(childContext, screen);
container.addView(newChild);
return newChild;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
View view = ((View) object);
container.removeView(view);
MortarScope.getScope(view.getContext()).destroy();
}
}
The problem statement: it's crashing, as SubView class hasn't been added into list of Injections at the "Layouts.createView(childContext, screen);" moment in the Adapter, and I can't add it by default, because I want to have a #provider of data from SubScreen to SubScreen.Presenter. (I'm using local variable.
If I add SubView.class into list of injections and convert local Screen's variables into static, then I'll have 3 identical pages inside the ViewPager (which is logical, as every next call of the constructor - overrides old static variables).
Any help/ideas?
Thanks for your help,
Konstantin
Ok, I figured out.
First of all, adding SubView into list of globally injected classes
Then modifying SubScreen class:
#Layout(R.layout.screen_subscreen)
public class SubScreen extends Path {
private static String titleStatic; // Introducing static variable
private final String title;
public SubScreen(String titleParam) {
title = titleParam;
}
public void refreshPresenter() {
titleStatic = title;
}
#Singleton
public static class Presenter extends ViewPresenter<SubView> {
private String title;
#Inject
public Presenter() {
}
#Override
protected void onLoad(Bundle savedInstanceState) {
super.onLoad(savedInstanceState);
if (!hasView()) {
return;
}
getView().setTitle(titleStatic);
}
}
}
and then in Custom adapter do this changes:
public class CustomPagerAdapter extends PagerAdapter {
private final Context context;
private final SubScreen[] screens;
public CustomPagerAdapter(Context context, SubScreen[] screens) {
this.context = context;
this.screens = screens;
}
......
#Override
public Object instantiateItem(ViewGroup container, int position) {
SubScreen screen = screens[position];
MortarScope originalScope = MortarScope.getScope(context);
MortarScope newChildScope = originalScope.buildChild().build("tutorialpage" + position);
Context childContext = newChildScope.createContext(context);
screen.refreshPresenter(); // updating the static var with local one!
View newChild = Layouts.createView(childContext, screen);
container.addView(newChild);
return newChild;
}
....
}
I.e. the solution is to keep the local AND static variables in the Screen, if the same screen is going to be reused. And when we inflate the view it - just setting the right value to the static one (that would be used in the Presenter).
I am not sure, that it is the best possible solution, but it works. It would be nice to hear, if it can be improved.
Related
I'm trying to populate a RecyclerView by following the nexts steps:
Download data from server and getting a SoapObject (yah, old server)
Transform the data to Flowable<List<MyItem>> (in Repository) in order to subscribe to it (in ViewModel) through LiveDataStreams.fromPublisher(flowableObj)
Set the resulted list into a MediatorLiveData object.
Observe the MediatorLiveData object in the Fragment's onViewCreated method.
So, when I click an on item from the list, it navigates (through Navigation Component) to a new Fragment, but, once I go back through the phone's back button, the list becomes empty and consequently the observer is notified and updates the list (shows nothing cause is empty).
I don't know why, the list gets empty and therefore the RecyclerView. Any help? -- code below:
Generic Fragment
public abstract class ListFragment<T> extends Fragment {
protected ListViewModel mViewModel;
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mNavController = Navigation.findNavController(getDataBindingObject().getRoot());
showItemsList();
setUpFilters();
}
protected void showItemsList() {
mViewModel.getList().observe(getViewLifecycleOwner(), listObserver);
mViewModel.getItemSelected().observe(getViewLifecycleOwner(), onListItemSelected());
}
protected final Observer<List<T>> listObserver = new Observer<List<T>>() {
#Override
public void onChanged(List<T> list) {
mViewModel.setListAdapter(list);
}
};
MyItem Fragment's code:
#Override
public View onCreateView(#NotNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mViewModel = new ViewModelProvider(this, new ViewModelFactory()).get(MyItemViewModel.class);
mDataBinding = ...
mDataBinding.setLifecycleOwner(this); //geViewLifecycleOwner()
mDataBinding.setViewModel(mViewModel);
return mDataBinding.getRoot();
}
#Override
public void onViewCreated(#NonNull #NotNull View view, #Nullable #org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mNavController = NavHostFragment.findNavController(this);
}
Generic ViewModel:
public abstract class ListViewModel<T, K> extends MyViewModel {
protected ListRepository<T> mRepository;
protected MediatorLiveData<List<T>> list;
protected MutableLiveData<K> mListAdapter;
public ListViewModel() {
super();
}
public LiveData<List<T>> getList() {
if (list == null) {
LiveData<List<T>> lD = LiveDataReactiveStreams.fromPublisher(mRepository.getList());
list = new MediatorLiveData<>();
list.addSource(lD, li -> {
this.list.postValue(li);
list.remove(lD); //removing this line does not work either
});
}
return list;
}
public LiveData<K> getListAdapter() {
if (mListAdapter == null)
mListAdapter = new MutableLiveData<>();
return mListAdapter;
}
public abstract void setListAdapter(List<T> list);
MyItemViewModel:
public class MyItemViewModel extends ListViewModel<MyItem, MyItemAdapter> {
protected MyItemRepository mHistoryRepository;
public MyItemViewModel(MyItemRepository repository) {
super();
mRepository = repository;
}
#Override
public void setListAdapter(List<MyItem> list) {
if (getListAdapter().getValue() == null) {
MyItemAdapter adapter = new MyItemAdapter(list);
adapter.setListener(onListItemSelectedListener);
mListAdapter.setValue(adapter);
} else
mListAdapter.getValue().updateList(list);
}
Generic Repository
public abstract class ListRepository<T> {
protected Flowable<List<T>> list;
protected abstract Flowable<List<T>> getItemsList(int orderByField);
public Flowable<List<T>> getList() {
if (list == null)
list = getItemsList();
return list;
}
MyItemRepository:
public class MyItemRepository extends ListRepository<MyItem> {
protected static volatile MyItemRepository instance;
protected final MyItemLocalDS mLocalDataSource;
protected final MyItemRemoteDS mRemoteDataSource;
public MyItemRepository(MyItemRemoteDS remoteDataSource,
MyItemLocalDS localDataSource) {
this.mRemoteDataSource = remoteDataSource;
this.mLocalDataSource = localDataSource;
}
public static MyItemRepository getInstance(MyRemoteDS remoteDataSource,
MyLocalDS localDataSource) {
if (instance == null)
instance = new MyItemRepository(remoteDataSource, localDataSource);
return instance;
}
#Override
protected Flowable<List<MyItem>> getItemsList() {
list = mRemoteDataSource.download(...)
.map(soapObject -> parseItemsList(soapObject))
.map(wsResult -> transformItemsList(wsResult));
return list.subscribeOn(Schedulers.io());
}
I am using retrofit2 for fetching data from the server and after fetching saving data in room database and then showing in recycler view. But it is no displayed (my data what I get using retrofit). I try display it in my fragment. In file ...data/data/databese/source.db these data are saved. I see it. So, that means that my code works. But I can't understand why it is not displayed.
my database class:
#Database(entities = {Source.class}, exportSchema = false, version = 1)
public abstract class SourceDatabase extends RoomDatabase {
private static final String DB_NAME = "source.db";
public abstract SourceDao sourceDao();
private static SourceDatabase instance;
public static SourceDatabase getInstance(Context context) {
if (instance == null) {
instance =buildDatabaseInstance(context);
}
return instance;
}
private static SourceDatabase buildDatabaseInstance(Context context) {
return Room.databaseBuilder(context,
SourceDatabase.class,
DB_NAME).build();
}
}
repository:
public class DataBaseRepository {
private static DataBaseRepository dataBaseRepository;
private SourceDao sourceDao;
private LiveData<List<Source>> allSourcestoDb;
private Context context;
public static DataBaseRepository getInstance(Context context) {
if (dataBaseRepository == null) {
dataBaseRepository = new DataBaseRepository(context);
}
return dataBaseRepository;
}
public DataBaseRepository(Context context) {
this.context = context;
SourceDatabase db = SourceDatabase.getInstance(context);
sourceDao = db.sourceDao();
allSourcestoDb = sourceDao.getSources();
}
public void getSourceListTodb(String key) {//отправка данных в LiveData
RestClient restClient = RestClient.getInstance();
restClient.startRetrofit();
restClient.getServerApi().getNews(key).enqueue(new Callback<News>() {
#Override
public void onResponse(Call<News> call, Response<News> response) {
Completable.fromAction(new Action (){
#Override
public void run() throws Exception {
if (response.body() != null) {
List<Source> list = response.body().getSources();
sourceDao.insert(list);
}
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CompletableObserver() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onComplete() {
}
#Override
public void onError(Throwable e) {
}
});
}
#Override
public void onFailure(Call<News> call, Throwable t) {
Log.d("error", "Can't parse data " + t);
}
});
}
public LiveData<List<Source>> getAllSourcestoDb() {
return allSourcestoDb;
}
}
dao:
#Dao
public interface SourceDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(List<Source> sources);
#Query("SELECT * FROM source")
LiveData<List<Source>> getSources();
}
viewModel:
public class SourceViewModel extends AndroidViewModel {
private DataBaseRepository dataBaseRepository;
private LiveData<List<Source>> allSources; //for db
public SourceViewModel(#NonNull Application application) {
super(application);
dataBaseRepository =DataBaseRepository.getInstance(application); //for db
allSources = dataBaseRepository.getAllSourcestoDb();
}
public LiveData<List<Source>> getAllSources() {
return allSources;
}
}
and fragment:
public class SavedDataFragment extends Fragment {
private SourceViewModel sourceViewModel;
private DataBaseRepository dataBaseRepository;
private RecyclerView recyclerView;
private List<Source> sourceList;
private SavedDataAdapter adapter;
public SavedDataFragment() {
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.saved_data,container,false);
DataSharedPreference sharedPreference = DataSharedPreference.getSPInstance();
String api_key = sharedPreference.loadText(getActivity());
dataBaseRepository = new DataBaseRepository(getActivity());
sourceViewModel = ViewModelProviders.of(this).get(SourceViewModel.class);
recyclerView = view.findViewById(R.id.recyclerViewSavedFragment);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(view.getContext()));
sourceList = new ArrayList<>();
adapter = new SavedDataAdapter(getActivity(), sourceList);
recyclerView.setAdapter(adapter);
sourceViewModel.getAllSources().observe(this, new Observer<List<Source>>() {
#Override
public void onChanged(List<Source> sources) {
adapter.setSourceList(sourceList);
}
});
dataBaseRepository.getSourceListTodb(api_key);
return view;
}
}
adapter:
public class SavedDataAdapter extends RecyclerView.Adapter<SavedDataAdapter.SourceSavedViewHolder> {
private LayoutInflater inflater;
private List<Source> sources;
public SavedDataAdapter(Context context, List<Source> sources) {
this.sources = sources;
this.inflater = LayoutInflater.from(context);
}
#NonNull
#Override
public SavedDataAdapter.SourceSavedViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = inflater.inflate(R.layout.saved_item, parent, false);
return new SourceSavedViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull final SavedDataAdapter.SourceSavedViewHolder holder, int position) {
final Source source = sources.get(position);
holder.sourceId.setText(source.getId());
holder.sourceName.setText(source.getName());
holder.sourceDescription.setText(source.getDescription());
holder.sourceURL.setText(source.getUrl());
holder.sourceCategory.setText(source.getCategory());
holder.sourceLanguage.setText(source.getLanguage());
holder.sourceCountry.setText(source.getCountry());
}
#Override
public int getItemCount() {
return sources.size();
}
public void setSourceList(List<Source> sources) {
this.sources = sources;
notifyDataSetChanged();
}
public static class SourceSavedViewHolder extends RecyclerView.ViewHolder {
TextView sourceName, sourceId, sourceDescription, sourceURL, sourceCategory, sourceLanguage, sourceCountry;
public SourceSavedViewHolder(View view) {
super(view);
sourceName = view.findViewById(R.id.sourceName);
sourceId = view.findViewById(R.id.sourceIdItem);
sourceDescription = view.findViewById(R.id.sourceDescription);
sourceURL = view.findViewById(R.id.sourceURL);
sourceCategory = view.findViewById(R.id.sourceCategory);
sourceLanguage = view.findViewById(R.id.sourceLanguage);
sourceCountry = view.findViewById(R.id.sourceCountry);
}
}
}
In your Fragment inside onChanged,
you're setting adapter.setSourceList(sourceList) where sourceList is an empty arrayList.
You should instead setSourceList to sources which is the updated list passed as an argument to onChanged method
That is :-
sourceViewModel.getAllSources().observe(this, new Observer<List<Source>>() {
#Override
public void onChanged(List<Source> sources) {
adapter.setSourceList(sources); // sources and not sourceList
}
});
Also there are few more things that should be taken care of.
For ex- in your observe method, you have passed this as first argument which is wrong when using Fragments as it may causes memory leaks. Instead you should pass viewLifeOwner..
More can found on this link Use viewLifecycleOwner as the LifecycleOwner
Try ti change this:
#Query("SELECT * FROM source")
To:
#Query("SELECT * FROM Source")
I have 2 view models observing 2 tables in room each emitting live data, they should update my recycler view when a value changes. My adapter is equipped to handle more than one model and view holder, but I'm not sure how to update the recycler views adapter with new data without overwriting the current data or duplicating any data any ideas?
So my adapter takes a list of Visitable (Visitable pattern)
I have 2 objects that implement this interface, the interface has a type so I can tell what view holder it wants and I update the recycler view using diff utils, it look like this
public class CardAdapter extends RecyclerView.Adapter<BaseViewHolder> {
private final List<Visitable> elements;
private final TypeFactory typeFactory;
private final ItemTouchListener onItemTouchListener;
private final Context context;
private String cardType;
private final String layoutIdentifier;
private static final String TAG = "Adptr-Card";
private String CARD_CLICK_UPDATE = "card_click_update";
private final String[] imageFilePathNames;
private RequestManager glide;
public CardAdapter(List<Visitable> elements, TypeFactory typeFactory, ItemTouchListener onItemTouchListener,
Context context,
String cardType, String layoutIdentifier, RequestManager glide) {
this.glide = glide;
this.elements = elements;
this.typeFactory = typeFactory;
this.onItemTouchListener = onItemTouchListener;
this.context = context;
this.cardType = cardType;
this.layoutIdentifier = layoutIdentifier;
this.imageFilePathNames = context.getResources().getStringArray(R.array.image_set_names);
}
#NonNull
#Override
public BaseViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View contactView = LayoutInflater.from(context).inflate(viewType, parent, false);
return typeFactory.createViewHolder(contactView, viewType, onItemTouchListener, glide, cardType);
}
#Override
public void onBindViewHolder(#NonNull BaseViewHolder holder, int position) {
holder.bind(elements.get(position), position);
}
#Override
public int getItemViewType(int position) {
return elements.get(position).type(typeFactory);
}
public void setCardType(String cardType) {
this.cardType = cardType;
notifyDataSetChanged();
}
#Override
public int getItemCount() {
return elements.size();
}
public List<Visitable> getList() {
return elements;
}
public List<Sentence> getSentencesList() {
ArrayList<Sentence> sentences = new ArrayList<>();
for (Visitable visitable : elements) {
if (visitable.type(typeFactory) == CardViewHolder.LAYOUT) {
sentences.add((Sentence) visitable);
}
}
return sentences;
}
public Visitable getItem(int position) {
if (position > 0 && position < elements.size()) {
return elements.get(position);
}
return elements.get(0);
}
class CalculateDiffUtils extends AsyncTask<Void, Void, DiffResult> {
private final List<Visitable> oldCardList;
private final List<Visitable> newCardList;
CalculateDiffUtils(List<Visitable> oldCardList, List<Visitable> newCardList) {
this.oldCardList = oldCardList;
this.newCardList = newCardList;
}
#Override
protected DiffUtil.DiffResult doInBackground(Void... params) {
return DiffUtil.calculateDiff(new VisitableDiffUtils(oldCardList, newCardList, typeFactory));
}
#Override
protected void onPostExecute(DiffUtil.DiffResult diffResult) {
super.onPostExecute(diffResult);
dispatchUpdates(diffResult, newCardList);
}
}
private void dispatchUpdates(DiffUtil.DiffResult diffResult, List<Visitable> newCardList) {
this.elements.clear();
this.elements.addAll(newCardList);
diffResult.dispatchUpdatesTo(this);
}
public void refreshDiffUtilsList(List<Visitable> sentences) {
new CalculateDiffUtils(elements, sentences).execute();
}
public void removeItem(int position) {
elements.remove(position);
notifyItemRemoved(position);
}
public void addCard(Sentence sentence) {
elements.add(getItemCount(), sentence);
notifyItemInserted(getItemCount());
}
public void addGroup(GroupsWithSentences sentence) {
elements.add(getItemCount(), sentence);
notifyItemInserted(getItemCount());
}
public void updateCardClick(int position) {
notifyItemChanged(position, CARD_CLICK_UPDATE);
}
public void refreshList(List<Visitable> newElements) {
ArrayList<Visitable> elementArrayList = new ArrayList<>(newElements);
elements.clear();
elements.addAll(elementArrayList);
notifyDataSetChanged();
}
}
My 2 view models sit in a fragment, they observe some data from my Room database and are updated when changes happen, but this means I will only ever have the data from one of the view models, I guess I want a way to combine these view models maybe using some kind of mediator live data, here are my 2 view models (I've removed stuff for brevity, they are both initiated using factories)
GROUP VIEW MODEL
public class GroupViewModel extends ViewModel {
private final GroupRepository groupRepository;
private final LiveData<List<GroupsWithSentences>> groups;
public GroupViewModel(#NonNull Application application, String[] cardArgs) {
groupRepository = new GroupRepository(application);
groups = groupRepository.getGroupsByWordDescriptionAndWordType(cardArgs[0],cardArgs[1]);
}
public LiveData<List<GroupsWithSentences>> getGroups() {
return groups;
}
}
SENTENCE VIEW MODEL
public class CardViewModel extends ViewModel {
private final SentenceRepository sentenceRepository;
private final LiveData<List<Sentence>> cards;
private static final String TAG = "view_model";
public CardViewModel(#NonNull Application application , int clicks){
sentenceRepository = new SentenceRepository(application);
search = new MutableLiveData<>();
cardArgs = new MutableLiveData<>();
cards = Transformations.switchMap(search, mySearch -> sentenceRepository.searchLiveCardListByWordTypeAndWordDescriptionAndSearchWord(getCardArgs()[0],getCardArgs()[1],mySearch));
}
public LiveData<List<Sentence>> getLiveCardList(){
return cards;
}
}
CALLING ADAPTER IN MY FRAGMENT
private void setUpCardViewModelObserver(String[] args) {
cardViewModel.getLiveCardList().observe(getViewLifecycleOwner(), sentenceList -> {
if (sentenceList != null) {
ArrayList<Visitable> list = new ArrayList<>(sentenceList);
cardAdapter.refreshDiffUtilsList(list);
checkResults(list.size());
}
});
}
private void setUpGroupViewModelObserver() {
groupViewModel.getGroups().observe(getViewLifecycleOwner(), groupsWithSentencesList -> {
if (groupsWithSentencesList != null) {
ArrayList<Visitable> list = new ArrayList<>(groupsWithSentencesList);
cardAdapter.refreshDiffUtilsList(list);
checkResults(groupsWithSentencesList.size());
}
});
}
Any help is welcome, many thanks.
So the answer was to use Mediator Live data, i set the new mediator live data to respond to changes to my existing live data objects and then mediate those changes so i now only have one stream of data so my card view model now looks like this
public CardViewModel(#NonNull Application application , int clicks, String[] cardArgs){
sentenceRepository = new SentenceRepository(application);
search = new MutableLiveData<>();
cards = Transformations.switchMap(search, mySearch -> sentenceRepository.searchLiveCardListByWordTypeAndWordDescriptionAndSearchWord(cardArgs[0],cardArgs[1],mySearch));
groupRepository = new GroupRepository(application);
groups = groupRepository.getGroupsByWordDescriptionAndWordType(cardArgs[0],cardArgs[1]);
sentencesAndGroups = new MediatorLiveData<>();
sentencesAndGroups.addSource(cards, sentences -> {
sentencesAndGroups.setValue(combineLatest(sentences, groups.getValue()));
});
sentencesAndGroups.addSource(groups, groupsWithSentences -> {
sentencesAndGroups.setValue(combineLatest(cards.getValue(), groupsWithSentences));
});
}
and my new combine latest method looks like this
private List<Visitable> combineLatest(List<Sentence> sentenceList, List<GroupsWithSentences> groupsWithSentences) {
List<Visitable> visitableList = new ArrayList<>();
if (sentenceList != null){
visitableList.addAll(sentenceList);
}
if (groupsWithSentences != null){
visitableList.addAll(groupsWithSentences);
}
return visitableList;
}
I'm using an app which contain viewPager and TapLayout. And a fragment which showed on ViewPager has Bluetooth connecting function. When I tab a button on a fragment, Ble Started.
But in that process, I got an
interface ClassCastException Error
A fragment implement interface which in BluetoothController. BluetoothController need context, so I passed context(getContext) which got in Fragment.
When I implement interface and pass context in MainActivity, it works well. But I do that in fragment, Android studio occurs 'interface ClassCastException Error'.
I should implement interface in fragment, because interface pass Bluetooth state like Connecting, DisConnecting, Start etc.
How can I set interface in fragment?
When MainActivity implement interface, Bluetooth interface works well. But that case, Bluetooth state is not passed in fragment. It pass state in MainActivity. I think when I got state in MainActivity, pass the state to fragment is one of the solution for this problem. But I think it is not good method.
here is BluetoothContoller code
public class BluetoothController extends BluetoothPacketController
{
public static final String TAG = BluetoothController.class.getSimpleName() +"_Debug";
private static Context context;
private static final int SCAN_PERIOD = 1000*10;
private int BLE_STATE = BLUETOOTH_STATE.IDLE;
private BleInterface mListener;
public class BLUETOOTH_STATE {
public static final int IDLE = 0;
public static final int SCANNING = 1;
public static final int CONNECTION_TRY = 2;
public static final int CONNECTION_ERR = 3;
public static final int CONNECTION_SUCC = 4;
public static final int DISCONNECTION = 5;
}
public interface BleInterface{
void bleScanTimeOut();
void bleScanStart();
void bleScanStop();
void bleConnectTry();
void bleDisConnectTry();
void bleErrDisConnectTry();
void bleDisConnected();
void bleResult(boolean ret);
void bleResult(String ret);
}
private static BluetoothController instance = null;
#Override
public void setContext(Context context) {
super.setContext(context);
this.context = context;
}
public static BluetoothController getInstance() {
if (instance == null) {
instance = new BluetoothController();
}
return instance;
}
#Override
public void init(Context context) {
super.init(context);
setContext(context);
mListener = (BluetoothController.BleInterface) context;//listener;//(BluetoothController.BleInterface) context;
}
...
}
And here is fragment code
public class RegistrationFragment extends Fragment implements BluetoothController.BleInterface{
private View view;
public static final String TAG = RegistrationFragment.class.getSimpleName() + "_Debug";
private static RegistrationFragment instance = null;
public static RegistrationFragment getInstance() {
if (instance == null) {
instance = new RegistrationFragment();
}
return instance;
}
public void setContext(Context context) {
this.context = context;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
view = inflater.inflate(R.layout.register_view, container, false);
BluetoothScanUpperAPI21.getInstance().init(getContext());
BluetoothScanUnderAPI21.getInstance().init(getContext());
BluetoothController.getInstance().init(getContext());
startUi_init();
return view;
}
...
}
I want to get Bluetooth state through BleInterface in fragment.
Why are you casting context in BleInterface? Can't you pass this from fragment to BluetoothController where this referes BleInterface which is implemented in fragment?
In Fragment
BluetoothController.getInstance().init(this);
BleController's Init:
#Override
public void init(BluetoothController.BleInterface ble) {
super.init(context);
setContext(context);
mListener = ble;
}
Reason for the behaviour that you are facing:
Fragment doesn't have their own context. They always refers to activity context. So, when you pass getContext(), it refers to activity context and compiler tries to find BleInterface in activity which does not exists. That's why you are facing ClassCastException.
I need to have a Relative Layout that's Serializable because I need to pass it in the Bundle of the creation of a newInstance of a Fragment (just casting it in to Serializable didn't work).
I did like this.
import java.io.Serializable;
public class SerializableRelativeLayout extends RelativeLayout implements Serializable {
public SerializableRelativeLayout(Context context){
super(context);
}
public SerializableRelativeLayout(Context context, AttributeSet attributeSet){
super(context, attributeSet);
}
public SerializableRelativeLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
}
In the layout files I use it like this:
<packagename.utils.SerializableRelativeLayout
It's working fine, but the problem is that if I go to the background, or start a new activity, when going back to the previous activity that contains a SerializableRelativeLayout, the app crashes with the messages:
java.io.InvalidClassException: android.widget.RelativeLayout; IllegalAccessException
and
java.lang.RuntimeException: Parcelable encountered IOException reading a Serializable object (name = packagename.utils.SerializableRelativeLayout
Fragments without this, I have no problem. So I'm guessing I need to save the instance of the fragments (I know I should already be doing this). Then comes the problem, I'm using a fragmentStatePageAdapter, and I couldn't find anywhere how to save it. These are my classes.
public class GalleryPagerAdapter extends FragmentStatePagerAdapter {
private List<Page> pageList;
private SerializableRelativeLayout parentActivity;
private String pathToTrack;
public GalleryPagerAdapter(FragmentManager fm, List<Page> pageList, SerializableRelativeLayout parentActivity, String pathToTrack) {
super(fm);
this.pageList = pageList;
this.parentActivity = parentActivity;
this.pathToTrack = pathToTrack;
}
#Override
public Fragment getItem(int i) {
return ContentGalleryFragment.newInstance(pageList.get(i), pageList.size(), parentActivity, pathToTrack);
}
#Override
public int getCount() {
return pageList.size();
}
}
And
public class ContentGalleryFragment extends Fragment {
private final static String PAGE = "page";
private final static String PAGE_COUNT = "pageCount";
private final static String RELATIVE_LAYOUT = "relativeLayout";
private final static String PATH = "pathToTrack";
private Page contentPage;
private int pagesCount;
private SerializableRelativeLayout relativeLayout;
private String pathToTrack;
public ContentGalleryFragment(){
}
public final static ContentGalleryFragment newInstance(Page contentPage, int pagesCount, SerializableRelativeLayout relativeLayout, String pathToTrack) {
ContentGalleryFragment fragment = new ContentGalleryFragment();
Bundle bundle = new Bundle(4);
bundle.putSerializable(PAGE, contentPage);
bundle.putInt(PAGE_COUNT, pagesCount);
bundle.putSerializable(RELATIVE_LAYOUT, relativeLayout);
bundle.putString(PATH, pathToTrack);
fragment.setArguments(bundle);
return fragment;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_content_gallery, container, false);
contentPage = (Page) getArguments().getSerializable(PAGE);
pagesCount = getArguments().getInt(PAGE_COUNT);
relativeLayout = (SerializableRelativeLayout) getArguments().getSerializable(RELATIVE_LAYOUT);
pathToTrack = getArguments().getString(PATH);
ContentDetailHelper contentHelper = new ContentDetailHelper(relativeLayout, pathToTrack);
contentHelper.createContentDetailsPage(contentPage, pagesCount, inflater, rootView.findViewById(R.id.fragment_content_gallery_linearLayout_root));
return rootView;
}
#Override
public void onSaveInstanceState(final Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable(PAGE, contentPage);
outState.putInt(PAGE_COUNT, pagesCount);
outState.putSerializable(RELATIVE_LAYOUT , relativeLayout);
outState.putString(PATH, pathToTrack);
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
//probably orientation change
contentPage = (Page) savedInstanceState.getSerializable(PAGE);
pagesCount = savedInstanceState.getInt(PAGE_COUNT);
relativeLayout = (SerializableRelativeLayout) savedInstanceState.getSerializable(RELATIVE_LAYOUT);
pathToTrack = savedInstanceState.getString(PATH);
}
}
}
I know we should Override onSaveInstanceState when we are doing a simple Activty / Fragment . But I couldn't find how to do with a ViewPager.
It's my first question here, so, sorry if it's too long or not well explained/wrong title.
Thanks in advance.