0

I'm developing a flutter app and I've used ChangeNotifierProvider to manage states.I have a class called 'Data' which is the model of the app (model in MVC design pattern) and a class called 'DataManager' which is the controller of the app (controller in MVC design pattern) and makes an instance of Data in it. I've made instance of ChangeNotifierProvider in my main and the ChangeNotifier is DataManager. So the methods in DataManager call notifyListeners() method. When I run the app, I add a tile and the UI it won't change although the tile is added. After I add another tile the first one appears and so on.The app is always one level behind the user. Can you help me fix this problem?

This is main.dart:

main(){
  runApp(MyApp());
}

class MyApp extends StatefulWidget{
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => DataManager(),
      child: MaterialApp(
          home: LoadingScreen()
      ),
    );
  }
}

This is Data.dart (It's methods might not be important here):

class Data{
  Position _location;
  List<CityTile> _cityWidgets = List<CityTile>();
  List<Weather> _weatherDatas = List<Weather>();

  Future<Position> getLocation() async {
    bool serviceEnabled;
    LocationPermission permission;

    serviceEnabled = await Geolocator.isLocationServiceEnabled();
    if (!serviceEnabled) {
      print('Location services are disabled.');
      return Future.error('Location services are disabled.');
    }

    permission = await Geolocator.checkPermission();
    if (permission == LocationPermission.deniedForever) {
      print('Location permissions are permanently denied, we cannot request permissions.');
      return Future.error(
          'Location permissions are permanently denied, we cannot request permissions.');
    }

    if (permission == LocationPermission.denied) {
      permission = await Geolocator.requestPermission();
      if (permission != LocationPermission.whileInUse &&
          permission != LocationPermission.always) {
        print( 'Location permissions are denied (actual value: $permission).');
        return Future.error(
            'Location permissions are denied (actual value: $permission).');
      }
    }

    _location = await Geolocator.getCurrentPosition();
    return _location;
  }

  void addCity({Weather cityWeather}) async{
    bool isReady = await cityWeather.updateWeather();
    String cityName = cityWeather.getCity;
    if(!cityExists(cityName) && isReady) {
      _weatherDatas.add(cityWeather);
      _cityWidgets.add(CityTile(cityName));
    }
    // print("widgets:");
    // for(CityTile cityTile in _cityWidgets){
    //   print("${cityTile.city} widget exists");
    // }
    // print("weathers:");
    // for(Weather weather in _weatherDatas){
    //   print("${weather.getCity} weather exists");
    // }
  }

  Weather searchWeather({String cityName}){
    for(Weather weather in _weatherDatas){
      if(weather.getCity == cityName){
        return weather;
      }
    }
    return null;
  }

  bool cityExists(String cityName){
    if(searchWeather(cityName: cityName) == null)
      return false;
    else
      return true;
  }

  void removeCity({String cityName}) {
    if (cityExists(cityName)) {
      _removeCityWidget(cityName: cityName);
      _removeCityWeather(cityName: cityName);
    }
  }

  void _removeCityWidget({String cityName}){
    CityTile cityTileToRemove;
    for(CityTile cityTile in _cityWidgets){
      if(cityTile.city == cityName){
        cityTileToRemove = cityTile;
      }
    }
    if(cityTileToRemove != null)
      _cityWidgets.remove(cityTileToRemove);
  }

  void _removeCityWeather({String cityName}){
    Weather weather = searchWeather(cityName: cityName);
    if(weather != null)
      _weatherDatas.remove(weather);
  }

  int widgetNumbers(){
    return _cityWidgets.length;
  }

  get weatherDatas{
    return List.unmodifiable(_weatherDatas);
  }

  get cityWidgets{
    return List.unmodifiable(_cityWidgets);
  }
}

This is DataManager.dart:

class DataManager extends ChangeNotifier{
  Data data = Data();

  Future<bool> findWeatherByLocation() async{
    Position location = await data.getLocation();
    // print("long : ${location.longitude} and lat : ${location.latitude}");
    Weather weatherOfHere = Weather(city: null);
    String weatherCast = "";

    if(location == null){
      // print("location is null");
      return false;
    }

    for(int i=0; i<5; i++){
      weatherCast = await weatherOfHere.getCurrentWeather(location: location);
      if(weatherCast.isNotEmpty)
        break;
    }
    if( weatherCast.isEmpty || jsonDecode(weatherCast)['cod'] == '404') {
      // print("city not found");
      return false;
    }
    // print("weathercast : $weatherCast");

    addCityByWeather(weatherOfHere);
    return true;
  }

  void addCityByWeather(Weather cityWeather){
    data.addCity(cityWeather: cityWeather);
    notifyListeners();
  }

  void addCityByName(String city) async{
    if(!data.cityExists(city) && city.isNotEmpty){
      Weather cityWeather = Weather(city: city);
      bool isRealCity = await cityWeather.updateWeather();
      if(isRealCity) {
        data.addCity(cityWeather: cityWeather);
      }
    }
    notifyListeners();
  }

  void removeCity(String city){
    data.removeCity(cityName: city);
    notifyListeners();
  }

  int cityNumbers(){
    return data.widgetNumbers();
  }

  Future<bool> updateWeather(String city) async{
    Weather weatherToUpdate = data.searchWeather(cityName: city);
    bool isUpdated = false;
    if(weatherToUpdate == null){
      return false;
    }
    else{
      isUpdated = await weatherToUpdate.updateWeather();
      notifyListeners();
    }
    return isUpdated;
  }

  get weatherDatas{
    return data.weatherDatas;
  }

  get cityWidgets{
    return data.cityWidgets;
  }

  void addOption(String option){
    option = option.toLowerCase() == 'feels like' ? 'feels_like' : option;
    options[option.toLowerCase()] = true;
    //updateAll();
    notifyListeners();
  }

  void removeOption(String option){
    option = option.toLowerCase() == 'feels like' ? 'feels_like' : option;
    options[option.toLowerCase()] = false;
    // updateAll();
    notifyListeners();
  }

  void updateAll(){
    for(Weather weather in data.weatherDatas)
      weather.updateWeather();
    notifyListeners();
  }

  bool isOptionSelected(String option){
    option = option.toLowerCase() == 'feels like' ? 'feels_like' : option;
    // print("in isOptionSelected: ${options[option.toLowerCase()]}");
    return options[option.toLowerCase()];
  }

  Color getOptionButtonColor(String option){
    option = option.toLowerCase() == 'feels like' ? 'feels_like' : option;
    return isOptionSelected(option) ? Colors.indigo : Colors.black38;
  }

  get getOptions{
    return options;
  }

  String getWeatherScreenPicture(String city){
    Weather weatherData = data.searchWeather(cityName: city);
    int id = weatherData.id;

    if(id == 800){
      var now = new DateTime.now();
      List clearSky = codeToPicture[800];
      if( now.hour> 18 ) {
        return clearSky[1];
      }else
        return clearSky[0];
    }
    return codeToPicture[id];
  }

  String getWeatherInfo(String city, String field){
    Weather weather = data.searchWeather(cityName: city);
    if(weather != null){
      switch(field){
        case 'temperature':
          return weather.temperature;
        case 'pressure':
          return weather.pressure;
        case 'humidity':
          return weather.humidity;
        case 'weather description':
          return weather.weatherDescription;
        case 'wind speed':
          return weather.windSpeed;
        case 'feels_like':
          return weather.feelsLike;
      }
    }
    return "null";
  }

  IconData getWeatherIcon(String city){
    Weather weather = data.searchWeather(cityName: city);
    if(weather != null)
      return weather.icon;
    else
      return WeatherIcons.refresh;
  }
}

There is also a listView.Builder which adds these tiles( city widgets ):

class CitiesScreen extends StatelessWidget {
  final TextEditingController _textEditingController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      bottomNavigationBar: NavigationBar(),
      backgroundColor: Colors.lightBlue,
      body: SafeArea(
        child: Padding(
          padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 10),
          child: Column(
            children: <Widget>[
              ColorfulBox(
                ListTile(
                title: TextField(
                  controller: _textEditingController,
                  style: TextStyle(fontSize: 20, color: Colors.white),),
                trailing: SizedBox(
                  width: 100,
                  child: Row(
                    children: [
                      SizedBox(
                        width: 50,
                        child: FlatButton(
                          child: Icon(Icons.add, size: 30, color: Colors.white,),
                          onPressed: () {
                              Provider.of<DataManager>(context, listen: false).addCityByName(_textEditingController.text);
                              _textEditingController.clear();
                          },
                        ),
                      ),
                      SizedBox(
                        width: 50,
                        child: FlatButton(
                            onPressed: () => Provider.of<DataManager>(context, listen: false).findWeatherByLocation(),
                            child: Icon(Icons.location_on_outlined, size: 30, color: Colors.white,),
                        ),
                      )
                    ],
                  ),
                ),
              ),
                  ),
              SizedBox(height: 30,),
              Expanded(
                child: Consumer<DataManager>(
                  builder: (context, data, child){
                    return ListView.builder(
                      shrinkWrap: true,
                      scrollDirection: Axis.vertical,
                      itemCount: Provider.of<DataManager>(context).cityNumbers(),
                      itemBuilder: (context, index) => Provider.of<DataManager>(context).cityWidgets[index],
                    );
                  },
                ),
              )
            ],
          ),
        ),
      ),
    );
  }

}

1 Answers1

0

Insert

Provider.of<DataManager>(context);

to build Function.

It listens when you call notifyListeners() and updates the UI.

CEO tech4lifeapps
  • 885
  • 1
  • 12
  • 31
James Lee
  • 11
  • 1