Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@wojtekno
Copy link
Owner

I got feedback from the reviewer, but

  1. I'm not sure how to understand some of it,
  2. not sure if I implemented the changes correctly

@wojtekno
Copy link
Owner Author

wojtekno commented Jul 20, 2020

That's the feedback I got.
it is written to the subbmited_solution state of code.
The post below I've highlighted a few points most relevant for me.

Application works and looks fine, but doesn't have a wow effect.
The project meets most of the expectations. The functionalities are here; however, there's little Kotlin code, no tests, and no architecture.
Most of the tasks which could block the main thread works on background thread.
The project structure is ok, but configuration could be more optimized. There's no tool used to help separate concerns.
There is no mess in the project, but there are some places which could be cleaned/improved before submitting the solution. Many lines of code can be simplified.
Most views don't have the logic inside, they are readable, with some exceptions. Business logic is coupled to Android SDK classes.
You write good Java code, but some parts could be implemented in more efficient way.
I assume that you found Event class on the web, perhaps in this article: https://medium.com/androiddevelopers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150
I would like to see how you write Kotlin code instead.
You know most of basic Android SDK things, use Android components properly, but some things could be organized in more efficient way.
Almost all XMLs are well structured. Layouts are mostly well defined and reusable.
The project doesn't contain any tests.
The project contains common libraries, however some of the SDK classes or external libraries could be replaced with better ones. Unfortunately you didn't use any library from our stack.
In MainFragment, you have such code:
if (someBool) { someField = true } else { someField = false }
It’s clearer to just someField = someBool.
You tend to use getContext() which may lead to NullPointerException. The requireContext() method would provide a better insight into what happened in such case.
There are multiple constructs where inside some LiveData observer you have a single-branched conditional instruction. This code would greatly benefit from filter() method known from libraries like RxJava.
You implement SensorEventListener in MainFragment which couples code.
There’s no clear separation of architecture layer. You made a domain package, but inside are Android SDK dependencies.
You use ViewModel where most of the logic dwells, but it’s strongly coupled to Android Data Binding, so I can’t take it as an MVVM.

@wojtekno
Copy link
Owner Author

I have a problem with a proper understanding of these below four points.
I implemented some solutions, but I would like to make sure and understand better what I did wrong the first attempt.

  1. Business logic is coupled to Android SDK classes.
  2. The project contains common libraries, however some of the SDK classes or external libraries could be replaced with better ones.
  3. There’s no clear separation of architecture layer. You made a domain package, but inside are Android SDK dependencies.
  4. You use ViewModel where most of the logic dwells, but it’s strongly coupled to Android Data Binding, so I can’t take it as an MVVM.

import timber.log.Timber;

//todo There’s no clear separation of architecture layer. You made a `domain` package, but inside are Android SDK dependencies.
public class LocationApiHandler extends LocationCallback {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I use this class as my data source. Is it OK from the architecture point of view?

@@ -0,0 +1,43 @@
package com.nowak.wjw.simplecompass.domain;

import android.location.Location;
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewer comment - "There’s no clear separation of architecture layer. You made a domain package, but inside are Android SDK dependencies."
I don't understand why Location cannot be here.
I need to take it and process it.

I can understand that it shouldn't be in viewModel or fragment,- but here? - why not?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Osoba która robiła ci review zwraca ci uwagę na to że warstwa domeny posiada zależności stricte z androida.
Generalnie z tego co widzę to próbujesz użyć wszystkiego na co zwrócisz uwagę na internecie, a brakuję ci wiedzy i doświadczenia żeby to wszystko spiąć w logiczną całość. Nawet radziłbym używać mniej złożonej architektury a starać się oddzielić warstwę prezentacji,danych i logiki.

package com.nowak.wjw.simplecompass;
package com.nowak.wjw.simplecompass.ui.main;

public enum CompassStateEnum {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where is the place for that enum? what package?

Comment on lines 32 to 33
private ActivityResultLauncher<String> requestPermissionLauncher =
registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the right place for that launcher? Please confirm or explain otherwise.

Comment on lines +60 to +63
mViewModel.needScreenOrientation.observe(getViewLifecycleOwner(), isNeeded -> {
if (isNeeded) {
int mScreenRotation = requireActivity().getWindowManager().getDefaultDisplay().getRotation();
mViewModel.provideScreenRotation(mScreenRotation);
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what would be a better way to do this?
putting it inside onResume didn't work cause when you flip your phone by 180degrees there is no onCreate triggered.

isRequestingLocationUpdates = true;
if (!foundLastLocation) configureLocation();
findButtonClicked();
btnClickAllowed();
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to deal with the buttonClicked directly in ViewModel, but I cannot figure out how to do that.
VM registers the click and need to check if permission granted.
If yes that's awesome, easy.
But if not, he needs to notify fragment about that ,and later if permission granted, the vm.buttonClicked has to be called once again with appropriate params - I don't know how to handle it

import com.nowak.wjw.simplecompass.domain.StartStopSensorListenerUseCase;

import timber.log.Timber;

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewers comment - "You use ViewModel where most of the logic dwells, but it’s strongly coupled to Android Data Binding, so I can’t take it as an MVVM."

I don't understand that

@@ -1,24 +0,0 @@
package com.nowak.wjw.simplecompass

open class Event<out T>(private val content: T) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pisałeś sam tę klasę czy skądś brałeś ? Czemu nie piszesz w kotlinie tak w ogóle ?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://medium.com/androiddevelopers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150
stwierdziłem, że opanuje pewne rzeczy najpierw w Javie (javaRx, dagger itp) a później będę się uczył kotlina.
Ale zaczłąem już się uczyć, bo po ogłoszeniach na pracę, widze , że bez kotlina to ciężko

@holakmateusz
Copy link

holakmateusz commented Jul 23, 2020 via email

Copy link

@holakmateusz holakmateusz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

zrobiłem ci review

// calculate th rotation matrix
SensorManager.getRotationMatrixFromVector(rMat, event.values);
int lAzimuth = (int) (Math.toDegrees(SensorManager.getOrientation(rMat, orientation)[0]) + 360) % 360;
return lAzimuth;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return w 1 linii

public LiveData<Boolean> hideKeyBoard;
public LiveData<Boolean> showLocationError;
private MutableLiveData<Boolean> mShouldProvideRationale = new MutableLiveData<>(false);
public LiveData<Event<Boolean>> shouldProvideRationale = Transformations.map(mShouldProvideRationale, should -> new Event(should));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lambda ?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pytasz, czy sugerujesz?
jak dla mnie to lambda jest ;P

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

można to uproscic

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nie jest to dla mnie widoczne na pierwszy rzut oka, zastanowie sie i dam znać

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

chodzi mi o to że można zrobić odwołanie do elementu klasy, u ciebie by to było Event::new

private MutableLiveData<Boolean> mStartLocationUpdates = new MutableLiveData<>(false);
private MutableLiveData<Integer> mScreenOrientation = new MutableLiveData<>(0);
private LiveData<Integer> mAzimuth;
private MutableLiveData<LocationCoordinates> mDestinationCoordinates = new MutableLiveData<>();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generalnie mam wrażenie że tego nie wiesz ale możę się mylę: mutableLiveData jest ukrywana po to żeby nikt z zewnątrz jej nie modyfikował - wystawiamy liveData jako immutableData które można obserwować. PRzez to że wszędzie używasz dataBinding używasz liveData co jest w sumie spoko.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

intencjonalnie pola MutableLiveData są prywatne, a wszystko co udostępniam, udostępniam jako LiveDate - właśnie, żeby nie można było przy tym grzebać.

public LiveData<Event<Boolean>> requestLocationPermission = Transformations.map(mRequestLocationPermission, request -> new Event<>(request));


public MainViewModel(GetAzimuthUseCase getAzimuthUseCase, StartStopSensorListenerUseCase startStopSensorListenerUseCase, InitiateLastLocationUseCase initiateLastLocationUseCase, RequestAndStopLocationUpdatesUseCase requestAndStopLocationUpdatesUseCase, GetDestinationBearingUseCase getDestinationBearingUseCase) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ten konstruktor jest tragicznie długi, samo formatowanie jużby pomogło.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

co do tego konstruktora to mam pytanie - czy inicjalizować te zmienne w konstruktorze, czy lepiej utworzyć metody które będą je ekspozycjonować, np

public LiveData getDestArrowRotation(){
return Transformations.switchMap(mDestinationBearing, b -> {
Timber.d("destArrowRotation");
if (b == null) return null;
return Transformations.map(needleRotation, r -> {
Timber.d("needleRotation");
return r + b;
});
});

tylko, że wtedy za każdym razem kiedy wywołany tę metodę to będziemy na nowo wywoływać ten kod, a tak to tylko raz go wywołujemy - plus Transformacje go wywołują, ale to i tak by się działo...

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

po to masz blok inicjalizujący init żeby takie startowe operacje wykonać w momencie użycia viewModelu.

Copy link

@holakmateusz holakmateusz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 uwaga

class Event<out T>(private val content: T) {

var hasBeenHandled = false
private set // Allow external read but not write

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

property jako private i setter wtedy będzie zbędny

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tak w ogole ta klasa dla mnie ma mały sens, funkcjonalnie ją rozumiem ale jest bez sensu napisana. JAk bym coś kopiowal to juz jakies dobre rozwiazania a nie jakis kod pisany na kolanie przez kogos.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no ja jeszcze nie do końca potrafie odróżnić jedno od drugiego LOL

} else {
hasBeenHandled = true
content
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

        when (hasBeenHandled) {
            true -> null
            false -> {
                hasBeenHandled = true
                content
            }
        }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants