3
votes

ViewModel n'a pas d'erreur de constructeur d'argument zéro - même s'il a un constructeur d'argument zéro

Je suis nouveau sur Android et Java et j'essaie de créer une application basée sur la localisation.

ÉDITER

J'ai créé un code de test beaucoup plus simple et j'obtiens la même erreur. Voici le java:

LocationViewModel locationViewModel = new ViewModelProvider(this).get(LocationViewModel.class);

J'ai la même erreur. Voici les dépendances dans mon build.gradle au niveau de l'application:

 public class LocationViewModel extends ViewModel {
        private LocationLiveData locationLiveData;

        public LocationViewModel () {
            locationLiveData = new LocationLiveData(getApplicationContext());
        }

        public LocationLiveData getLocationLiveData() {
            return locationLiveData;
        }
    }

POSTE ORIGINAL

J'essaie d'utiliser ViewModel et LiveData pour mettre à jour l'emplacement de l'utilisateur car je comprends que c'est la meilleure façon d'être conscient du cycle de vie. J'ai une activité de cartes par défaut ...

public class LocationLiveData extends LiveData<Location> {
        private final Context context;
        private FusedLocationProviderClient fusedLocationClient;
        private LocationRequest locationRequest;

        public LocationLiveData(Context context) {
            this.context = context;
            this.fusedLocationClient = LocationServices.getFusedLocationProviderClient(context);
        }

        private void setLocationData(Location location) {
            Location value = new Location("SetInternal");
            value.setLatitude(location.getLatitude());
            value.setLongitude(location.getLongitude());
            setValue(value);
        }

        protected void createLocationRequest() {
            LocationRequest locationRequest = LocationRequest.create();
            locationRequest.setInterval(1000);
            locationRequest.setFastestInterval(500);
            locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
        }

        private LocationCallback locationCallback = new LocationCallback() {
            @Override
            public void onLocationResult(LocationResult locationResult) {
                if (locationResult == null) {
                    return;
                }
                for (Location location : locationResult.getLocations()) {
                    setLocationData(location);
                }
            }
        };

        private void startLocationUpdates() {
            createLocationRequest();
            fusedLocationClient.requestLocationUpdates(locationRequest,
                    locationCallback,
                    Looper.getMainLooper());
        }

        @Override
        protected void onInactive() {
            super.onInactive();
            fusedLocationClient.removeLocationUpdates(locationCallback);
        }

        @Override
        protected void onActive() {
            super.onActive();
            fusedLocationClient.getLastLocation()
                    .addOnSuccessListener(new OnSuccessListener<Location>() {
                        @Override
                        public void onSuccess(Location location) {
                            if (location != null)
                                setValue(location);
                        }
                    });
            startLocationUpdates();
        }
    }

Une classe qui étend LiveData pour stocker l'emplacement de l'utilisateur ...

public class MapsActivity extends AppCompatActivity implements OnMapReadyCallback {...}

Et une classe qui étend ViewModel pour permettre à l'activité principale d'atteindre le LocationLiveData.

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

    //dependencies for ViewModel, LiveData, etc.
    def lifecycle_version = "2.2.0"
    def arch_version = "2.1.0"

    // ViewModel
    implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
    // LiveData
    implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
    // Lifecycles only (without ViewModel or LiveData)
    implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version"
    // Saved state module for ViewModel
    implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"
    // Annotation processor
    annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
}

Ensuite, lorsque j'essaye de créer une instance de locationViewModel dans la méthode onMapReady:

package com.example.viewmodeltest;

import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;

import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    public class MyViewModel extends ViewModel {
        public int scoreTeamA = 0;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyViewModel locationViewModel = new ViewModelProvider(this).get(MyViewModel.class);
    }
}

J'obtiens une erreur sur cette ligne:

Impossible de créer une instance de la classe com.example.MapsActivity $ LocationViewModel

Causé par: java.lang.InstantiationException: java.lang.Class n'a pas de constructeur d'argument zéro

J'obtiens cette erreur même si je retire le constructeur entièrement dans locationViewModel, et aussi si j'essaye d'étendre AndroidViewModel à la place.

Des idées? J'ai vu d'autres requêtes similaires, mais la réponse a toujours été de retirer des arguments du constructeur - ce que j'ai déjà fait!

Merci beaucoup pour toute aide


4 commentaires

"Et une classe qui étend ViewModel pour permettre à l'activité principale d'atteindre le LocationLiveData" - Je ne m'attendrais pas à ce que cela compile, car il n'y a pas de méthode getApplicationContext() dans ViewModel . LocationViewModel est-il peut-être déclaré comme une classe interne de quelque chose d'autre, comme une activité, qui a une méthode getApplicationContext() ?


Merci @CommonsWare. J'ai édité l'article avec un code de test plus simple qui n'implique pas cette ligne getApplicationContext () et j'obtiens la même erreur. Re: le getApplicationContext, mon approche originale consistait à passer un contexte dans le LocationViewModel à la place, mais je l'ai retiré pour obtenir un constructeur sans argument. Je n'ai pas de cours autres que ceux indiqués, et c'est ma seule activité jusqu'à présent!


@CommonsWare - je me demande si vous pouvez offrir un autre conseil. Je cherche à modifier la fréquence des mises à jour de l'emplacement en fonction de la proximité de l'utilisateur par rapport à un point donné (défini dynamiquement dans l'interface utilisateur). Dans onMapReady, j'observe le LocationLiveData. Je pensais que je pourrais avoir un objet locationRequest LiveData qui est mis à jour par mon observateur et est lui-même observé dans LocationLiveData; ou mon observateur pourrait appeler une méthode locationViewModel qui invoque une méthode LocationLiveData pour redémarrer les mises à jour avec de nouveaux paramètres. Une idée qui (le cas échéant) est plus robuste?


Désolé, mais je suppose que je ne comprends pas votre situation. Je vous recommande de poser une question distincte de Stack Overflow, dans laquelle vous pouvez fournir plus de détails.


4 Réponses :


2
votes

Soit:

  • Déplacez MyViewModel vers un fichier Java distinct, ou

  • Faire de MyViewModel une static class

À l'heure actuelle, vous avez défini MyViewModel comme une classe interne de MainActivity . Cela ne fonctionnera pas, car seule une instance de MainActivity peut créer une instance de MyViewModel . En particulier, ViewModelProvider ne peut pas créer une instance de MyViewModel .


2 commentaires

Merci beaucoup, c'était bien le problème! J'ai travaillé pour l'exemple du jouet, puis après quelques ajustements, le vrai code a également fonctionné. Comme vous l'avez dit, getApplicationContext () ne fonctionnait pas une fois que j'ai fait de LocationViewModel une classe statique, j'ai donc donné à LocationViewModel une méthode appelée setContext, où dans le code principal je pourrais simplement lui passer le contexte de l'application (au cas où cela serait utile à quiconque ).


Vous pouvez également revenir à AndroidViewModel , qui gère le cas où vous avez besoin d'un Context . En particulier, cela vous oblige à utiliser l' Application comme Context , vous évitant d'utiliser accidentellement l' Activity comme Context et de provoquer une fuite de mémoire. Mais, peu importe, je suis heureux que cela fonctionne pour vous!



3
votes

Quand tu écris

public class MainActivity extends AppCompatActivity {

    public static class MyViewModel extends ViewModel {
       public int scoreTeamA = 0;
    }
}

Cela fait de MyViewModel une classe interne de MainActivity , ce qui signifie qu'il a une référence implicite à la classe externe (selon ladocumentation Java ) et ne peut pas être construit séparément de MainActivity . Cela signifie effectivement qu'en bytecode, son constructeur prend toujours une instance de MainActivity - même si vous n'écrivez pas ce constructeur.

Vous pouvez faire de votre classe interne une classe imbriquée statique en ajoutant le mot clé static . Cela supprime la référence implicite à la classe externe et la rend similaire à toute autre classe de niveau supérieur:

public class MainActivity extends AppCompatActivity {

    public class MyViewModel extends ViewModel {
       public int scoreTeamA = 0;
    }
}


1 commentaires

Merci d'avoir fait de MyViewModel une classe statique pour résoudre le problème. Je ne savais pas vraiment ce que signifiait le mot-clé static alors merci pour l'explication!



1
votes

lors de l'utilisation de la poignée, je suis confronté au même problème lorsque j'ai oublié d'ajouter

@AndroidEntryPoint

au-dessus de la déclaration d'activité


0 commentaires

0
votes

dans mon cas, j'ai oublié d'ajouter HasSupportFragmentInjector à mon Activity !!!

public class MainActivity extends AppComaptActivity
        implements HasSupportFragmentInjector{


@Override
public void onCreate(Bundle savedInstanceState) {
    AndroidInjection.inject(this);
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    @Inject
    DispatchingAndroidInjector<Fragment> fragmentDispatchingAndroidInjector;


    @Override
    public AndroidInjector<Fragment> supportFragmentInjector() {
        return fragmentDispatchingAndroidInjector;
    }
}


0 commentaires