I have a very difficult time trying to implement Dagger2 to my android app.
In my app I use RoomDB. In the beginning, I used one MainActivity class and I have successfully implemented RoomDB. It worked as aspected. I could set and get values from my database. However, later I decided to expand my app by moving my logic from MainActivity class to fragments where each fragment is identical just it writes data or gets data from a database based on a different type of product. I have tried to make this work by implementing dependency injection Dagger2. I have watched plenty of videos and read a bunch of articles on how to do this however no luck so far.
I tried to implement one example based on the article Integrate Dagger 2 with Room Persistence Library in few lines
In order not to destroy my existing application I have created a new project with the same structure as in the article. in the new project, I have used the same data structure as I have in my application. The application builds successfully. However, I can't retrieve my data from the database and I don't know why.
Here is what I did:
Product.class
#Entity(tableName = "product_table")
public class Product implements Serializable {
#PrimaryKey(autoGenerate = true)
private int ID;
#ColumnInfo(name = "product_count")
private String count;
#ColumnInfo(name = "time")
private String time;
#ColumnInfo(name = "p_type")
private String product_type;
public String getProduct_type() {
return product_type;
}
public void setProduct_type(String product_type) {
this.product_type = product_type;
}
public int getID() {
return ID;
}
public void setID(int ID) {
this.ID = ID;
}
public String getCount() {
return count;
}
public void setCount(String count) {
this.count = count;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
}
ProductDao
#Dao
public interface ProductDao {
#Insert(onConflict = REPLACE)
void insert(Product product);
#Delete
void delete (Product product);
#Delete
void reset(List<Product> product);
#Query("UPDATE product_table SET product_count=:sCount WHERE ID = :sID")
void update(int sID, String sCount);
#Query("SELECT * FROM product_table")
List<Product> getAll();
#Query("SELECT SUM(product_count)FROM product_table WHERE p_type = 'used' ")
String getProductTotalCount();
}
AppComponent
#Singleton
#Component(dependencies = {}, modules = {AppModule.class, RoomModule.class})
public interface AppComponent {
void inject(MainActivity mainActivity);
ProductDao productDao();
DemoDatabase2 demoDatabase2();
ProductRepository productRepository();
Application application();
}
AppModule
#Module
public class AppModule {
Application mApplication;
public AppModule(Application application) {
mApplication = application;
}
#Provides
#Singleton
Application providesApplication() {
return mApplication;
}
}
RoomModule
#Module
public class RoomModule {
private DemoDatabase2 demoDatabase2;
public RoomModule(Application mApplication) {
demoDatabase2 = Room.databaseBuilder(mApplication, DemoDatabase2.class, "demo-db").build();
}
#Singleton
#Provides
DemoDatabase2 providesRoomDatabase() {
return demoDatabase2;
}
#Singleton
#Provides
ProductDao providesProductDao(DemoDatabase2 demoDatabase2) {
return demoDatabase2.getProductDao();
}
#Singleton
#Provides
ProductRepository productRepository(ProductDao productDao) {
return new ProductDataSource(productDao);
}
}
DemoDatabase2
#Database(entities = {Product.class}, version = DemoDatabase2.VERSION, exportSchema = false)
public abstract class DemoDatabase2 extends RoomDatabase {
static final int VERSION = 2;
public abstract ProductDao getProductDao();
}
ProductDataSource
public class ProductDataSource implements ProductRepository {
private ProductDao productDao;
#Inject
public ProductDataSource(ProductDao productDao) {
this.productDao = productDao;
}
#Override
public void insert(Product product) {
}
#Override
public void delete(Product product) {
}
public String provideProductCount() {
return productDao.getProductTotalCount();
}
#Override
public List<Product> getAll() {
return productDao.getAll();
}
}
ProductRepository
public interface ProductRepository {
void insert(Product product);
void delete(Product product);
List<Product> getAll();
String provideProductCount();
}
MainActivity
public class MainActivity extends AppCompatActivity {
#Inject
public ProductRepository productRepository;
TextView mTextView;
EditText mEditText, mEditTextType;
Button mButton;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerAppComponent.builder()
.appModule(new AppModule(getApplication()))
.roomModule(new RoomModule(getApplication()))
.build()
.inject(this);
mTextView = findViewById(R.id.text);
mEditTextType = findViewById(R.id.product_type);
mEditText = findViewById(R.id.productCount);
mButton = findViewById(R.id.button);
}
public void addValue(View view) {
Product product = new Product();
String count = mEditText.getText().toString();
product.setCount(count);
String type = mEditTextType.getText().toString();
product.setProduct_type(type);
mTextView.setText(String.valueOf(product.getCount()));
}
}
How can I get or set data as provideProductCount() from my DB? As if I try to use something like
String producCount = productRepository.provideProductCount();
It gives error as:
Caused by: java.lang.IllegalStateException: A migration from 1 to 2 was required but not found. Please provide the necessary Migration path via RoomDatabase.Builder.addMigration(Migration ...) or allow for destructive migrations via one of the RoomDatabase.Builder.fallbackToDestructiveMigration* methods.
at androidx.room.RoomOpenHelper.onUpgrade(RoomOpenHelper.java:117)
How to fix this?
You have to use fallbackToDestructiveMigration() to allow Room recreate database when you change db
public RoomModule(Application mApplication) {
demoDatabase2 = Room.databaseBuilder(mApplication, DemoDatabase2.class, "demo-db")
.fallbackToDestructiveMigration()
.build();
}
Visit here for more detail
First of all, As I can see your error doesn't relate to the Dagger. For migrating the database check this link:
https://developer.android.com/training/data-storage/room/migrating-db-versions
And the way you provided the context for creating the database is wrong.
I built a demo project with Dagger, RoomDB, but It was written by Kotlin.
https://github.com/frank-nhatvm/expensestracker
I hope that can help you. Check in the di/AppComponent.kt to know to provide a context using Dagger.
Related
I am currently working on a project where I have created the following custom Repository:
public interface ServiceRepository<T extends ServiceEntity> extends JpaRepository<T, UUID>, ServiceRepositoryCustom {
}
public interface ServiceRepositoryCustom {
List<ServiceEntity> findAllContainingName(String query);
}
#Repository("Repo")
public class ServiceRepositoryCustomImpl implements ServiceRepositoryCustom {
private final EntityManager em;
public ServiceRepositoryCustomImpl(EntityManager em) {
System.out.println("I got constructed");
this.em = em;
}
#Override
public List<ServiceEntity> findAllContainingName(String name) {
System.out.println("I got called with: " + name);
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<ServiceEntity> cq = cb.createQuery(ServiceEntity.class);
Root<ServiceEntity> serviceEntity = cq.from(ServiceEntity.class);
List<Predicate> predicates = new ArrayList<>();
if(name != null) {
// predicates.add(cb.equal(serviceEntity.get("name"), name));
predicates.add(cb.like(serviceEntity.get("name"), name + "%"));
}
cq.where(predicates.toArray(predicates.toArray(new Predicate[0])));
return em.createQuery(cq).getResultList();
}
}
The print statement "I got called with: " never gets called. So for whatever reason Spring Boot is not running the method through my custom implementation.
Any suggestions? Any help is much appreciated
Edit:
Here is the code that injects and uses the Repository in question
#Repository
public interface PineappleServiceRepository extends ServiceRepository<PineappleServiceEntity> {
}
#Component("Registry")
#DependsOn({"Context", "Repo"})
public class Registry {
private final List<ServiceRepository<? extends ServiceEntity>> serviceRepositories = new ArrayList<>();
public Registry(PineappleServiceRepository pineappleServiceRepository) {
this.serviceRepositories.add(pineappleServiceRepository);
}
}
Edit 2:
The code prints "I got constructed"
Edit 3:
Class where findAllContainingName is called
#RestController
#RequestMapping("/test")
#DependsOn("Registry")
public class ServiceController {
private final Registry registry;
public ServiceController(#NotNull Registry registry) {
this.registry = registry;
}
#GetMapping("")
List<ServiceEntity> all(#RequestParam("q") String query) {
return getAllServices(query);
}
private #NotNull List<ServiceEntity> getAllServices(String query) {
List<ServiceEntity> response = new ArrayList<>();
for(ServiceRepository<? extends ServiceEntity> repo: this.registry.getServiceRepositories()){
response.addAll(repo.findAllContainingName(query));
}
return response;
}
}
Edit 4:
Here the entities:
#Entity
#Table(name = "services")
public abstract class ServiceEntity {
protected #Id
UUID id = UUID.randomUUID();
protected String name;
// Constructor + Getters and Setters
}
#Entity
public class PineappleServiceEntity extends ServiceEntity {
// Additional Properties, matching Constructors, Getters and Setters
}
So I was able to reproduce your problem and fix it. Issue with your code is that your PineappleServiceRepository is not extending ServiceRepositoryCustom directly. It seems your repository needs to implement it directly if you are accessing custom repository methods from that repository. I got that idea from this post.
So to fix your issue, either remove PineappleServiceRepository(as you don't have any properties in PineappleEntity) and use ServiceRepository to call that custom method or make PineappleServiceRepository extend ServiceRepositoryCustom.
I have pushed changes to GitHub with fix. You can take a look. If you want to keep PineappleServiceRepository and access custom method using this repository, let me know, I can update code.
I have a project in spring boot and I'm using CrudRepository, but when I try to update, it doesn't do anything.
#Entity
public class PfmSelection implements Serializable{
#Id
private Integer releaseId;
private String preparedBy;
}
Repositiry
#Repository
public interface IPfmSelectionDao extends CrudRepository<PfmSelection, Integer> {
}
Service
public interface IPfmSelectionService {
public PfmSelection save(PfmSelection pfmSelection);
public PfmSelection findById(Integer id);
}
Service Impl
#Service
public class PfmSelectionService implements IPfmSelectionService {
#Autowired
private IPfmSelectionDao pfmSelectionDao;
#Override
#Transactional
public PfmSelection save(PfmSelection pfmSelection) {
return this.pfmSelectionDao.save(pfmSelection);
}
#Override
#Transactional(readOnly = true)
public PfmSelection findById(Integer id) {
return this.pfmSelectionDao.findById(id).orElse(null);
}
}
Service where I use the other Service
#Autowired
private IPfmSelectionService pfmSelectionService;
private void updatePfm(PushModel pushModel) {
PfmSelection pfm = this.pfmSelectionService.findById(167427);
pfm.setPreparedBy("Rodrige");
pfmSelectionService.save(pfm);
}
I don't receive any error in the console.
You need to take a few steps to know what the problem is
Take the return of pfmSelectionService.save(pfm) and print the saved instance returned like below:
private void updatePfm(PushModel pushModel) {
PfmSelection pfm = this.pfmSelectionService.findById(167427);
pfm.setPreparedBy("Rodrige");
PfmSelection pfm2 = pfmSelectionService.save(pfm);
System.out.println(pfm2.getPreparedBy());
}
Put logger/debugger inside the save method, before and after the save method and check for the entry/exit sop/logger statements in log/console like
#Override
#Transactional
public PfmSelection save(PfmSelection pfmSelection) {
System.out.println("Inside save method");
PfmSelection pfmSelectionSaved =
this.pfmSelectionDao.save(pfmSelection);
System.out.println("Exits save method");
return pfmSelectionSaved;
}
Check for any Aop around advice or any place where the exception is being caught but eaten/not thrown further.
Check if there is any update query fired in the logs at the time of save call.
Also check if the setter method pfm.setPreparedBy("Rodrige"); is Empty?
I'm creating an application based on Hazelcast and spring-boot-starter-web. The application structure is:
Book controller-> BookService interface-> BookServiceImplementation-> put into hazelcast queue
Author controller-> AuthorService interface-> AuthorServiceImplementation-> put into hazelcast queue.
For that purpose i need one class that contains hazelcastInstance to share it in all services so i created blackboard interface but because i use #Autowired it is creating new instance for every service and i need to set hazelcast instance again.
My code so far:
Controller:
#GetMapping("book")
public ResponseEntity<Void> getBookDetails(
#RequestParam(value = "bookId", required = false) Long bookId) {
bookService.add(bookId);
return new ResponseEntity<Void>(HttpStatus.OK);
}
Service impl:
#Service("bookService")
public class BookServiceImpl extends BaseService implements BookService {
#Override
public void add(Long bookId) {
blackboard.add(bookId, Listeners.BOOK_QUEUE_NAME);
}
BaseService:
public class BaseService {
#Autowired
protected Blackboard blackboard;
public void loadInstance(ClientConfig clientConfig) {
HazelcastInstance hazelcastInstanceClient = HazelcastClient.newHazelcastClient(clientConfig);
blackboard.setHazelcastInstance(hazelcastInstanceClient);
}
}
Blackboard interface impl:
#Component("blackboard")
public class BlackboardImpl implements Blackboard {
private HazelcastInstance hazelcastInstance;
#Override
public HazelcastInstance getHazelcastInstance() {
return hazelcastInstance;
}
#Override
public void setHazelcastInstance(HazelcastInstance hazelcastInstance) {
this.hazelcastInstance = hazelcastInstance;
}
#Override
public boolean add(Object obj, String collectionId) {
IQueue<Object> queue = hazelcastInstance.getQueue(collectionId);
return queue.add(obj);
}
}
I was reading this link about Spring Data JPA and it got me curious: Instead of using #Query annotation, can you create a query and then use it as a param to the method?
More like this:
#Repository
public interface MyRepository extends CrudRepository<MyClass, Integer>
{
void doSomething(Query query);
}
(BTW, I know I could implement a fragment repository and solve my problem, but I'm curious)
you could not create an implementation class, instead of that you can write interface methods like this:
#Entity
public class Part {
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Id
private Long id;
#Column(unique = true)
private String partId;
public Part() {
}
public Part(String partId) {
this.partId = partId;
}
public String getPartId() {
return partId;
}
public void setPartId(String partId) {
this.partId = partId;
}
public Set<Card> getCards() {
return cards;
}
}
public interface PartRepository extends CrudRepository<Part, Long> {
public Optional<Part> findByPartId(String partId);
public List<Part> findAllByPartId(String partId);
}
Spring automatically convert these lines to SQL in background, you should don't care about that.
You can find some details here: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods
I have methods in Dao, in Repository, in ViewModel and in Activity to get list of Persons,
but I get empty list in the end (Null object reference error).
WHere can be a mistake? THanks a lott..
In PersonDao:
#Dao
public interface PersonDao {
//other methods of PersonDao
#Query("SELECT * FROM person_table WHERE status = :status ORDER BY RANDOM() LIMIT 5")
List<Person> getFivePersonsFrom(String status);
}
In PersonRepository:
public class PersonRepository {
private PersonDao mPersonDao;
private List<Person> mFivePersonsFrom;
//other methods
List<Person> getFivePersonsFrom(String status) {
PersonRoomDatabase.databaseWriteExecutor.execute(() -> {
mPersonDao.getFivePersonsFrom("noob");
});
return mFivePersonsFrom;
}
}
In PersonViewModel:
public class PersonViewModel extends AndroidViewModel {
private PersonRepository mRepository;
public List<Person> mFivePersonsFrom;
public PersonViewModel(#NonNull Application application) {
super(application);
mRepository = new PersonRepository(application);
mFivePersonsFrom = mRepository.getFivePersonsFrom("noob");
//other methods
}
public List<Person> getFivePersonsFrom() {
mRepository.getFivePersonsFrom("noob");
return mFivePersonsFrom;
}
}
In MainActivity:
private CardStackView noobCardStackView;
private NoobAdapter noobAdapter;
List<Person> noobList;
// other
protected void onCreate(Bundle savedInstanceState) {
noobViewModel = new ViewModelProvider(this,
ViewModelProvider.AndroidViewModelFactory.getInstance(this.getApplication()))
.get(PersonViewModel.class);
noobList = noobiewModel.getFivePersonsFrom();
noobAdapter = new NoobAdapter(new NoobAdapter.NoobDiff(), noobList);
noobCardStackView.setAdapter(noobAdapter);
// methods
}