[DEVICE ONLY] - ERROR TypeError: Cannot read property 'null' of null


#1

This app loads data from Local Storage when it starts.
Everything is fine with “ionic serve” on chrome but when I run the app on device I have this error :

ERROR TypeError: Cannot read property ‘null’ of null.

It looks like i = null but I don’t know why.

home.ts

import { Component, } from '@angular/core';
import { NavController } from 'ionic-angular';
import { Storage } from '@ionic/storage';
import { AlertController } from 'ionic-angular';


@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {
  cards:any = [];
  i: number;
  introEnd: boolean;

  constructor(public navCtrl: NavController, private storage: Storage, private alertCtrl: AlertController) {
    this.i = 0;
    this.introEnd = false;
  }

  ngOnInit() {
    if (this.i = 0) {
      this.loadCards();
      console.log(this.cards);
      console.log("new cards loaded");
    }
    else {
      this.loadSavedCards();
    }
  }

  deleteStorage() {
    this.loadCards();
    console.log("game restart");
    this.i = 0;
    console.log(this.i);
    this.storage.set("i", this.i);
    this.introEnd = false;
  }

  loadCards() {
    this.cards = [
        {id: '1', question: '1', answer: 'kdckdlqkn.', hint: 'foeofu'},
        {id: '2', question: '2', answer: 'xnqspin', hint: 'xnsin'},
        {id: '3', question: '3', answer: 'xnqcksjd cjspin', hint: 'xnsij dljn'}, 
        {id: '4', question: '4', answer: 'xnqscljqs cpin', hint: 'xnscqns ljin'}, 
        {id: '5', question: '5', answer: 'xnlqjsnxqspin', hint: 'xnsqlksnxin'}, 
        {id: '6', question: '6', answer: 'knk', hint: 'xklnknsin'}, 
        {id: '7', question: '7', answer: 'xnqspin', hint: 'xnsin'},
        {id: '8', question: '8', answer: 'xnqcksjd cjspin', hint: 'xnsij dljn'}, 
        {id: '9', question: '9', answer: 'xnqscljqs cpin', hint: 'xnscqns ljin'}, 
        {id: '10', question: '10', answer: 'xnlqjsnxqspin', hint: 'xnsqlksnxin'}, 
        {id: '11', question: '11', answer: 'knk', hint: 'xklnknsin'}, 
        {id: '12', question: '12', answer: 'knk', hint: 'xklnknsin'}, 
    ];
    this.shuffleCards(this.cards)
    this.storage.set("shuffled", this.cards);
    }

  setI() {
    this.i++;
    console.log("i = " + this.i)
    this.storage.set("i", this.i);
  }

  loadSavedCards() {
    this.storage.get("shuffled").then(cards => {
      this.cards = cards;
      console.log("here are the saved cards " + this.cards);
    });
    this.storage.get("i").then(i => {
      this.i = i;
      console.log("i = " + this.i);
  });
  }

 shuffleCards(cards) {
    var currentIndex = cards.length, temporaryValue, randomIndex;
    while (0 !== currentIndex) {
      randomIndex = Math.floor(Math.random() * currentIndex);
      currentIndex -= 1;

      temporaryValue = cards[currentIndex];
      cards[currentIndex] = cards[randomIndex];
      cards[randomIndex] = temporaryValue;
    };
    console.log(this.cards);
  }
}

#2

Not sure if related, but this is a sadly common mistake. = is assignment, not comparison.


#3

Thanks, it was indeed a mistake, but not the solution. I made changes to avoid the if else condition in ngOnInit…


#4

Well, finally I still have the same error on device… Don’t know why i = null when app start.
Still works on browser


#5

This is a classic race condition. loadSavedCards() doesn’t return anything, so nobody has any clue when it has done its work. See this post. You must have a contingency plan in place to deal with i being in an indeterminate state (like a loading spinner).


#6

Thanks for your help and sorry for the time taken to answer.

I guess this is the choice you are pointing to solve this error. But I don’t really understand what you mean by “declare your function to return some sort of future” and how it applies to loadSavedCards(). Have you an example in mind so I can relate to it?


#7

I would move all the data-related stuff into a provider. Something like (untested, off the top of my head):

interface Card {
  id: string;
  question: string;
  answer: string;
  hint: string;
}

class DeckService {
  deck = new BehaviorSubject<Card[]>([]);
  pointer = new BehaviorSubject<number>(0);

  private _pristine: Card[] = [
     {id: '1', question: '1', answer: 'kdckdlqkn.', hint: 'foeofu'},
     // &c
   ];

  constructor(private _storage: Storage) {
    // read storage from previous run
    // this could be amended to shuffle new deck if nothing is found
    this._storage.get('cards').then(cards => {
      if (cards) { this.cards.next(cards); }
    });
    this._storage.get('pointer').then(p => {
      if (p) { this.pointer.next(p); }
    });
    // update storage on any modifications
    this.cards.subscribe(cards => this._storage.set('cards', cards));
    this.pointer.subscribe(p => this._storage.set('pointer', p));
  }

  newGame(): void {
    this.cards.next(this.shuffle(this._pristine));
    this.pointer.next(0);
  }
}

In client pages you can inject this service and subscribe to cards and pointer. They will automatically reflect the current state of affairs. If, for example, you have a page that looks like this:

i: number;
constructor(private _deck: DeckService) {
  this._deck.pointer.subscribe(i => this.i = i);
}

…then all you have to do to move the pointer is:

this._deck.pointer.next(this.i + 1);

That would take care of updating storage, as well as any other pages using this same service, without any additional effort.


#8

I can’t explain why, but after a few changes, it finally works. I will do what you recommand if it doesn’t work “the newbie way”.
Thanks again for your time and explanations.


#9

Best of luck, but race conditions are one of, if not the most insidious class of bugs. Seemingly unrelated things can cause them to go into hiding, but until you directly address the situation, they’re never really fixed.


#10

holly cow… ok, I should investigate around this provider then…
But there are still concepts I’m not really familiar with at the moment.